Initial commit.
authorThomas Pornin <pornin@bolet.org>
Sun, 30 Jul 2017 21:13:10 +0000 (23:13 +0200)
committerThomas Pornin <pornin@bolet.org>
Sun, 30 Jul 2017 21:13:10 +0000 (23:13 +0200)
93 files changed:
Asn1/AsnElt.cs [new file with mode: 0644]
Asn1/AsnException.cs [new file with mode: 0644]
Asn1/AsnIO.cs [new file with mode: 0644]
Asn1/AsnOID.cs [new file with mode: 0644]
Asn1/PEMObject.cs [new file with mode: 0644]
Crypto/AES.cs [new file with mode: 0644]
Crypto/BigInt.cs [new file with mode: 0644]
Crypto/BlockCipherCore.cs [new file with mode: 0644]
Crypto/ChaCha20.cs [new file with mode: 0644]
Crypto/CryptoException.cs [new file with mode: 0644]
Crypto/DES.cs [new file with mode: 0644]
Crypto/DSAUtils.cs [new file with mode: 0644]
Crypto/DigestCore.cs [new file with mode: 0644]
Crypto/EC.cs [new file with mode: 0644]
Crypto/ECCurve.cs [new file with mode: 0644]
Crypto/ECCurve25519.cs [new file with mode: 0644]
Crypto/ECCurvePrime.cs [new file with mode: 0644]
Crypto/ECCurveType.cs [new file with mode: 0644]
Crypto/ECDSA.cs [new file with mode: 0644]
Crypto/ECPrivateKey.cs [new file with mode: 0644]
Crypto/ECPublicKey.cs [new file with mode: 0644]
Crypto/GHASH.cs [new file with mode: 0644]
Crypto/HMAC.cs [new file with mode: 0644]
Crypto/HMAC_DRBG.cs [new file with mode: 0644]
Crypto/IBlockCipher.cs [new file with mode: 0644]
Crypto/IDigest.cs [new file with mode: 0644]
Crypto/IPrivateKey.cs [new file with mode: 0644]
Crypto/IPublicKey.cs [new file with mode: 0644]
Crypto/MD5.cs [new file with mode: 0644]
Crypto/ModInt.cs [new file with mode: 0644]
Crypto/MutableECPoint.cs [new file with mode: 0644]
Crypto/MutableECPointCurve25519.cs [new file with mode: 0644]
Crypto/MutableECPointPrime.cs [new file with mode: 0644]
Crypto/NIST.cs [new file with mode: 0644]
Crypto/Poly1305.cs [new file with mode: 0644]
Crypto/RFC6979.cs [new file with mode: 0644]
Crypto/RNG.cs [new file with mode: 0644]
Crypto/RSA.cs [new file with mode: 0644]
Crypto/RSAPrivateKey.cs [new file with mode: 0644]
Crypto/RSAPublicKey.cs [new file with mode: 0644]
Crypto/SHA1.cs [new file with mode: 0644]
Crypto/SHA224.cs [new file with mode: 0644]
Crypto/SHA256.cs [new file with mode: 0644]
Crypto/SHA2Big.cs [new file with mode: 0644]
Crypto/SHA2Small.cs [new file with mode: 0644]
Crypto/SHA384.cs [new file with mode: 0644]
Crypto/SHA512.cs [new file with mode: 0644]
SSLTLS/IO.cs [new file with mode: 0644]
SSLTLS/IServerChoices.cs [new file with mode: 0644]
SSLTLS/IServerPolicy.cs [new file with mode: 0644]
SSLTLS/ISessionCache.cs [new file with mode: 0644]
SSLTLS/InputRecord.cs [new file with mode: 0644]
SSLTLS/KeyUsage.cs [new file with mode: 0644]
SSLTLS/OutputRecord.cs [new file with mode: 0644]
SSLTLS/PRF.cs [new file with mode: 0644]
SSLTLS/RecordDecrypt.cs [new file with mode: 0644]
SSLTLS/RecordDecryptCBC.cs [new file with mode: 0644]
SSLTLS/RecordDecryptChaPol.cs [new file with mode: 0644]
SSLTLS/RecordDecryptGCM.cs [new file with mode: 0644]
SSLTLS/RecordDecryptPlain.cs [new file with mode: 0644]
SSLTLS/RecordEncrypt.cs [new file with mode: 0644]
SSLTLS/RecordEncryptCBC.cs [new file with mode: 0644]
SSLTLS/RecordEncryptChaPol.cs [new file with mode: 0644]
SSLTLS/RecordEncryptGCM.cs [new file with mode: 0644]
SSLTLS/RecordEncryptPlain.cs [new file with mode: 0644]
SSLTLS/SSL.cs [new file with mode: 0644]
SSLTLS/SSLClient.cs [new file with mode: 0644]
SSLTLS/SSLEngine.cs [new file with mode: 0644]
SSLTLS/SSLException.cs [new file with mode: 0644]
SSLTLS/SSLQuirks.cs [new file with mode: 0644]
SSLTLS/SSLServer.cs [new file with mode: 0644]
SSLTLS/SSLServerPolicyBasic.cs [new file with mode: 0644]
SSLTLS/SSLSessionCacheLRU.cs [new file with mode: 0644]
SSLTLS/SSLSessionParameters.cs [new file with mode: 0644]
Tests/Poly1305Ref.cs [new file with mode: 0644]
Tests/TestCrypto.cs [new file with mode: 0644]
Tests/TestEC.cs [new file with mode: 0644]
Tests/TestMath.cs [new file with mode: 0644]
Twrch/JSON.cs [new file with mode: 0644]
Twrch/MergeStream.cs [new file with mode: 0644]
Twrch/Twrch.cs [new file with mode: 0644]
X500/DNPart.cs [new file with mode: 0644]
X500/X500Name.cs [new file with mode: 0644]
XKeys/AlgorithmIdentifier.cs [new file with mode: 0644]
XKeys/KF.cs [new file with mode: 0644]
ZInt/ZInt.cs [new file with mode: 0644]
build.cmd [new file with mode: 0644]
build.sh [new file with mode: 0755]
conf/bearssl.json [new file with mode: 0644]
conf/eccert.pem [new file with mode: 0644]
conf/eckey.pem [new file with mode: 0644]
conf/rsacert.pem [new file with mode: 0644]
conf/rsakey.pem [new file with mode: 0644]

diff --git a/Asn1/AsnElt.cs b/Asn1/AsnElt.cs
new file mode 100644 (file)
index 0000000..9c6e1ab
--- /dev/null
@@ -0,0 +1,2315 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace Asn1 {
+
+/*
+ * An AsnElt instance represents a decoded ASN.1 DER object. It is
+ * immutable.
+ */
+
+public class AsnElt {
+
+       /*
+        * Universal tag values.
+        */
+       public const int BOOLEAN           = 1;
+       public const int INTEGER           = 2;
+       public const int BIT_STRING        = 3;
+       public const int OCTET_STRING      = 4;
+       public const int NULL              = 5;
+       public const int OBJECT_IDENTIFIER = 6;
+       public const int Object_Descriptor = 7;
+       public const int EXTERNAL          = 8;
+       public const int REAL              = 9;
+       public const int ENUMERATED        = 10;
+       public const int EMBEDDED_PDV      = 11;
+       public const int UTF8String        = 12;
+       public const int RELATIVE_OID      = 13;
+       public const int SEQUENCE          = 16;
+       public const int SET               = 17;
+       public const int NumericString     = 18;
+       public const int PrintableString   = 19;
+       public const int T61String         = 20;
+       public const int TeletexString     = 20;
+       public const int VideotexString    = 21;
+       public const int IA5String         = 22;
+       public const int UTCTime           = 23;
+       public const int GeneralizedTime   = 24;
+       public const int GraphicString     = 25;
+       public const int VisibleString     = 26;
+       public const int GeneralString     = 27;
+       public const int UniversalString   = 28;
+       public const int CHARACTER_STRING  = 29;
+       public const int BMPString         = 30;
+
+       /*
+        * Tag classes.
+        */
+       public const int UNIVERSAL   = 0;
+       public const int APPLICATION = 1;
+       public const int CONTEXT     = 2;
+       public const int PRIVATE     = 3;
+
+       /*
+        * Internal rules
+        * ==============
+        *
+        * Instances are immutable. They reference an internal buffer
+        * that they never modify. The buffer is never shown to the
+        * outside; when decoding and creating, copies are performed
+        * where necessary.
+        *
+        * If the instance was created by decoding, then:
+        *   objBuf   points to the array containing the complete object
+        *   objOff   start offset for the object header
+        *   objLen   complete object length
+        *   valOff   offset for the first value byte
+        *   valLen   value length (excluding the null-tag, if applicable)
+        *   hasEncodedHeader  is true
+        *
+        * If the instance was created from an explicit value or from
+        * sub-elements, then:
+        *   objBuf   contains the value, or is null
+        *   objOff   is 0
+        *   objLen   is -1, or contains the computed object length
+        *   valOff   is 0
+        *   valLen   is -1, or contains the computed value length
+        *   hasEncodedHeader  is false
+        *
+        * If objBuf is null, then the object is necessarily constructed
+        * (Sub is not null). If objBuf is not null, then the encoded
+        * value is known (the object may be constructed or primitive),
+        * and valOff/valLen identify the actual value within objBuf.
+        *
+        * Tag class and value, and sub-elements, are referenced from
+        * specific properties.
+        */
+
+       byte[] objBuf;
+       int objOff;
+       int objLen;
+       int valOff;
+       int valLen;
+       bool hasEncodedHeader;
+
+       AsnElt()
+       {
+       }
+
+       /*
+        * The tag class for this element.
+        */
+       int tagClass_;
+       public int TagClass {
+               get {
+                       return tagClass_;
+               }
+               private set {
+                       tagClass_ = value;
+               }
+       }
+
+       /*
+        * The tag value for this element.
+        */
+       int tagValue_;
+       public int TagValue {
+               get {
+                       return tagValue_;
+               }
+               private set {
+                       tagValue_ = value;
+               }
+       }
+
+       /*
+        * The sub-elements. This is null if this element is primitive.
+        * DO NOT MODIFY this array.
+        */
+       AsnElt[] sub_;
+       public AsnElt[] Sub {
+               get {
+                       return sub_;
+               }
+               private set {
+                       sub_ = value;
+               }
+       }
+
+       /*
+        * The "constructed" flag: true for an elements with sub-elements,
+        * false for a primitive element.
+        */
+       public bool Constructed {
+               get {
+                       return Sub != null;
+               }
+       }
+
+       /*
+        * The value length. When the object is BER-encoded with an
+        * indefinite length, the value length includes all the sub-objects
+        * but NOT the formal null-tag marker.
+        */
+       public int ValueLength {
+               get {
+                       if (valLen < 0) {
+                               if (Constructed) {
+                                       int vlen = 0;
+                                       foreach (AsnElt a in Sub) {
+                                               vlen += a.EncodedLength;
+                                       }
+                                       valLen = vlen;
+                               } else {
+                                       valLen = objBuf.Length;
+                               }
+                       }
+                       return valLen;
+               }
+       }
+
+       /*
+        * The encoded object length (complete with header).
+        */
+       public int EncodedLength {
+               get {
+                       if (objLen < 0) {
+                               int vlen = ValueLength;
+                               objLen = TagLength(TagValue)
+                                       + LengthLength(vlen) + vlen;
+                       }
+                       return objLen;
+               }
+       }
+
+       /*
+        * Check that this element is constructed. An exception is thrown
+        * if this is not the case.
+        */
+       public void CheckConstructed()
+       {
+               if (!Constructed) {
+                       throw new AsnException("not constructed");
+               }
+       }
+
+       /*
+        * Check that this element is primitive. An exception is thrown
+        * if this is not the case.
+        */
+       public void CheckPrimitive()
+       {
+               if (Constructed) {
+                       throw new AsnException("not primitive");
+               }
+       }
+
+       /*
+        * Get a sub-element. This method throws appropriate exceptions
+        * if this element is not constructed, or the requested index
+        * is out of range.
+        */
+       public AsnElt GetSub(int n)
+       {
+               CheckConstructed();
+               if (n < 0 || n >= Sub.Length) {
+                       throw new AsnException("no such sub-object: n=" + n);
+               }
+               return Sub[n];
+       }
+
+       /*
+        * Check that the tag is UNIVERSAL with the provided value.
+        */
+       public void CheckTag(int tv)
+       {
+               CheckTag(UNIVERSAL, tv);
+       }
+
+       /*
+        * Check that the tag has the specified class and value.
+        */
+       public void CheckTag(int tc, int tv)
+       {
+               if (TagClass != tc || TagValue != tv) {
+                       throw new AsnException("unexpected tag: " + TagString);
+               }
+       }
+
+       /*
+        * Check that this element is constructed and contains exactly
+        * 'n' sub-elements.
+        */
+       public void CheckNumSub(int n)
+       {
+               CheckConstructed();
+               if (Sub.Length != n) {
+                       throw new AsnException("wrong number of sub-elements: "
+                               + Sub.Length + " (expected: " + n + ")");
+               }
+       }
+
+       /*
+        * Check that this element is constructed and contains at least
+        * 'n' sub-elements.
+        */
+       public void CheckNumSubMin(int n)
+       {
+               CheckConstructed();
+               if (Sub.Length < n) {
+                       throw new AsnException("not enough sub-elements: "
+                               + Sub.Length + " (minimum: " + n + ")");
+               }
+       }
+
+       /*
+        * Check that this element is constructed and contains no more
+        * than 'n' sub-elements.
+        */
+       public void CheckNumSubMax(int n)
+       {
+               CheckConstructed();
+               if (Sub.Length > n) {
+                       throw new AsnException("too many sub-elements: "
+                               + Sub.Length + " (maximum: " + n + ")");
+               }
+       }
+
+       /*
+        * Get a string representation of the tag class and value.
+        */
+       public string TagString {
+               get {
+                       return TagToString(TagClass, TagValue);
+               }
+       }
+
+       static string TagToString(int tc, int tv)
+       {
+               switch (tc) {
+               case UNIVERSAL:
+                       break;
+               case APPLICATION:
+                       return "APPLICATION:" + tv;
+               case CONTEXT:
+                       return "CONTEXT:" + tv;
+               case PRIVATE:
+                       return "PRIVATE:" + tv;
+               default:
+                       return String.Format("INVALID:{0}/{1}", tc, tv);
+               }
+
+               switch (tv) {
+               case BOOLEAN:           return "BOOLEAN";
+               case INTEGER:           return "INTEGER";
+               case BIT_STRING:        return "BIT_STRING";
+               case OCTET_STRING:      return "OCTET_STRING";
+               case NULL:              return "NULL";
+               case OBJECT_IDENTIFIER: return "OBJECT_IDENTIFIER";
+               case Object_Descriptor: return "Object_Descriptor";
+               case EXTERNAL:          return "EXTERNAL";
+               case REAL:              return "REAL";
+               case ENUMERATED:        return "ENUMERATED";
+               case EMBEDDED_PDV:      return "EMBEDDED_PDV";
+               case UTF8String:        return "UTF8String";
+               case RELATIVE_OID:      return "RELATIVE_OID";
+               case SEQUENCE:          return "SEQUENCE";
+               case SET:               return "SET";
+               case NumericString:     return "NumericString";
+               case PrintableString:   return "PrintableString";
+               case TeletexString:     return "TeletexString";
+               case VideotexString:    return "VideotexString";
+               case IA5String:         return "IA5String";
+               case UTCTime:           return "UTCTime";
+               case GeneralizedTime:   return "GeneralizedTime";
+               case GraphicString:     return "GraphicString";
+               case VisibleString:     return "VisibleString";
+               case GeneralString:     return "GeneralString";
+               case UniversalString:   return "UniversalString";
+               case CHARACTER_STRING:  return "CHARACTER_STRING";
+               case BMPString:         return "BMPString";
+               default:
+                       return String.Format("UNIVERSAL:" + tv);
+               }
+       }
+
+       /*
+        * Get the encoded length for a tag.
+        */
+       static int TagLength(int tv)
+       {
+               if (tv <= 0x1F) {
+                       return 1;
+               }
+               int z = 1;
+               while (tv > 0) {
+                       z ++;
+                       tv >>= 7;
+               }
+               return z;
+       }
+
+       /*
+        * Get the encoded length for a length.
+        */
+       static int LengthLength(int len)
+       {
+               if (len < 0x80) {
+                       return 1;
+               }
+               int z = 1;
+               while (len > 0) {
+                       z ++;
+                       len >>= 8;
+               }
+               return z;
+       }
+
+       /*
+        * Decode an ASN.1 object. The provided buffer is internally
+        * copied. Trailing garbage is not tolerated.
+        */
+       public static AsnElt Decode(byte[] buf)
+       {
+               return Decode(buf, 0, buf.Length, true);
+       }
+
+       /*
+        * Decode an ASN.1 object. The provided buffer is internally
+        * copied. Trailing garbage is not tolerated.
+        */
+       public static AsnElt Decode(byte[] buf, int off, int len)
+       {
+               return Decode(buf, off, len, true);
+       }
+
+       /*
+        * Decode an ASN.1 object. The provided buffer is internally
+        * copied. If 'exactLength' is true, then trailing garbage is
+        * not tolerated (it triggers an exception).
+        */
+       public static AsnElt Decode(byte[] buf, bool exactLength)
+       {
+               return Decode(buf, 0, buf.Length, exactLength);
+       }
+
+       /*
+        * Decode an ASN.1 object. The provided buffer is internally
+        * copied. If 'exactLength' is true, then trailing garbage is
+        * not tolerated (it triggers an exception).
+        */
+       public static AsnElt Decode(byte[] buf, int off, int len,
+               bool exactLength)
+       {
+               int tc, tv, valOff, valLen, objLen;
+               bool cons;
+               objLen = Decode(buf, off, len,
+                       out tc, out tv, out cons,
+                       out valOff, out valLen);
+               if (exactLength && objLen != len) {
+                       throw new AsnException("trailing garbage");
+               }
+               byte[] nbuf = new byte[objLen];
+               Array.Copy(buf, off, nbuf, 0, objLen);
+               return DecodeNoCopy(nbuf, 0, objLen);
+       }
+
+       /*
+        * Internal recursive decoder. The provided array is NOT copied.
+        * Trailing garbage is ignored (caller should use the 'objLen'
+        * field to learn the total object length).
+        */
+       static AsnElt DecodeNoCopy(byte[] buf, int off, int len)
+       {
+               int tc, tv, valOff, valLen, objLen;
+               bool cons;
+               objLen = Decode(buf, off, len,
+                       out tc, out tv, out cons,
+                       out valOff, out valLen);
+               AsnElt a = new AsnElt();
+               a.TagClass = tc;
+               a.TagValue = tv;
+               a.objBuf = buf;
+               a.objOff = off;
+               a.objLen = objLen;
+               a.valOff = valOff;
+               a.valLen = valLen;
+               a.hasEncodedHeader = true;
+               if (cons) {
+                       List<AsnElt> subs = new List<AsnElt>();
+                       off = valOff;
+                       int lim = valOff + valLen;
+                       while (off < lim) {
+                               AsnElt b = DecodeNoCopy(buf, off, lim - off);
+                               off += b.objLen;
+                               subs.Add(b);
+                       }
+                       a.Sub = subs.ToArray();
+               } else {
+                       a.Sub = null;
+               }
+               return a;
+       }
+
+       /*
+        * Decode the tag and length, and get the value offset and length.
+        * Returned value if the total object length.
+        * Note: when an object has indefinite length, the terminated
+        * "null tag" will NOT be considered part of the "value length".
+        */
+       static int Decode(byte[] buf, int off, int maxLen,
+               out int tc, out int tv, out bool cons,
+               out int valOff, out int valLen)
+       {
+               int lim = off + maxLen;
+               int orig = off;
+
+               /*
+                * Decode tag.
+                */
+               CheckOff(off, lim);
+               tv = buf[off ++];
+               cons = (tv & 0x20) != 0;
+               tc = tv >> 6;
+               tv &= 0x1F;
+               if (tv == 0x1F) {
+                       tv = 0;
+                       for (;;) {
+                               CheckOff(off, lim);
+                               int c = buf[off ++];
+                               if (tv > 0xFFFFFF) {
+                                       throw new AsnException(
+                                               "tag value overflow");
+                               }
+                               tv = (tv << 7) | (c & 0x7F);
+                               if ((c & 0x80) == 0) {
+                                       break;
+                               }
+                       }
+               }
+
+               /*
+                * Decode length.
+                */
+               CheckOff(off, lim);
+               int vlen = buf[off ++];
+               if (vlen == 0x80) {
+                       /*
+                        * Indefinite length. This is not strict DER, but
+                        * we allow it nonetheless; we must check that
+                        * the value was tagged as constructed, though.
+                        */
+                       vlen = -1;
+                       if (!cons) {
+                               throw new AsnException("indefinite length"
+                                       + " but not constructed");
+                       }
+               } else if (vlen > 0x80) {
+                       int lenlen = vlen - 0x80;
+                       CheckOff(off + lenlen - 1, lim);
+                       vlen = 0;
+                       while (lenlen -- > 0) {
+                               if (vlen > 0x7FFFFF) {
+                                       throw new AsnException(
+                                               "length overflow");
+                               }
+                               vlen = (vlen << 8) + buf[off ++];
+                       }
+               }
+
+               /*
+                * Length was decoded, so the value starts here.
+                */
+               valOff = off;
+
+               /*
+                * If length is indefinite then we must explore sub-objects
+                * to get the value length.
+                */
+               if (vlen < 0) {
+                       for (;;) {
+                               int tc2, tv2, valOff2, valLen2;
+                               bool cons2;
+                               int slen;
+
+                               slen = Decode(buf, off, lim - off,
+                                       out tc2, out tv2, out cons2,
+                                       out valOff2, out valLen2);
+                               if (tc2 == 0 && tv2 == 0) {
+                                       if (cons2 || valLen2 != 0) {
+                                               throw new AsnException(
+                                                       "invalid null tag");
+                                       }
+                                       valLen = off - valOff;
+                                       off += slen;
+                                       break;
+                               } else {
+                                       off += slen;
+                               }
+                       }
+               } else {
+                       if (vlen > (lim - off)) {
+                               throw new AsnException("value overflow");
+                       }
+                       off += vlen;
+                       valLen = off - valOff;
+               }
+
+               return off - orig;
+       }
+
+       static void CheckOff(int off, int lim)
+       {
+               if (off >= lim) {
+                       throw new AsnException("offset overflow");
+               }
+       }
+
+       /*
+        * Get a specific byte from the value. This provided offset is
+        * relative to the value start (first value byte has offset 0).
+        */
+       public int ValueByte(int off)
+       {
+               if (off < 0) {
+                       throw new AsnException("invalid value offset: " + off);
+               }
+               if (objBuf == null) {
+                       int k = 0;
+                       foreach (AsnElt a in Sub) {
+                               int slen = a.EncodedLength;
+                               if ((k + slen) > off) {
+                                       return a.ValueByte(off - k);
+                               }
+                       }
+               } else {
+                       if (off < valLen) {
+                               return objBuf[valOff + off];
+                       }
+               }
+               throw new AsnException(String.Format(
+                       "invalid value offset {0} (length = {1})",
+                       off, ValueLength));
+       }
+
+       /*
+        * Encode this object into a newly allocated array.
+        */
+       public byte[] Encode()
+       {
+               byte[] r = new byte[EncodedLength];
+               Encode(r, 0);
+               return r;
+       }
+
+       /*
+        * Encode this object into the provided array. Encoded object
+        * length is returned.
+        */
+       public int Encode(byte[] dst, int off)
+       {
+               return Encode(0, Int32.MaxValue, dst, off);
+       }
+
+       /*
+        * Encode this object into the provided array. Only bytes
+        * at offset between 'start' (inclusive) and 'end' (exclusive)
+        * are actually written. The number of written bytes is returned.
+        * Offsets are relative to the object start (first tag byte).
+        */
+       int Encode(int start, int end, byte[] dst, int dstOff)
+       {
+               /*
+                * If the encoded value is already known, then we just
+                * dump it.
+                */
+               if (hasEncodedHeader) {
+                       int from = objOff + Math.Max(0, start);
+                       int to = objOff + Math.Min(objLen, end);
+                       int len = to - from;
+                       if (len > 0) {
+                               Array.Copy(objBuf, from, dst, dstOff, len);
+                               return len;
+                       } else {
+                               return 0;
+                       }
+               }
+
+               int off = 0;
+
+               /*
+                * Encode tag.
+                */
+               int fb = (TagClass << 6) + (Constructed ? 0x20 : 0x00);
+               if (TagValue < 0x1F) {
+                       fb |= (TagValue & 0x1F);
+                       if (start <= off && off < end) {
+                               dst[dstOff ++] = (byte)fb;
+                       }
+                       off ++;
+               } else {
+                       fb |= 0x1F;
+                       if (start <= off && off < end) {
+                               dst[dstOff ++] = (byte)fb;
+                       }
+                       off ++;
+                       int k = 0;
+                       for (int v = TagValue; v > 0; v >>= 7, k += 7);
+                       while (k > 0) {
+                               k -= 7;
+                               int v = (TagValue >> k) & 0x7F;
+                               if (k != 0) {
+                                       v |= 0x80;
+                               }
+                               if (start <= off && off < end) {
+                                       dst[dstOff ++] = (byte)v;
+                               }
+                               off ++;
+                       }
+               }
+
+               /*
+                * Encode length.
+                */
+               int vlen = ValueLength;
+               if (vlen < 0x80) {
+                       if (start <= off && off < end) {
+                               dst[dstOff ++] = (byte)vlen;
+                       }
+                       off ++;
+               } else {
+                       int k = 0;
+                       for (int v = vlen; v > 0; v >>= 8, k += 8);
+                       if (start <= off && off < end) {
+                               dst[dstOff ++] = (byte)(0x80 + (k >> 3));
+                       }
+                       off ++;
+                       while (k > 0) {
+                               k -= 8;
+                               if (start <= off && off < end) {
+                                       dst[dstOff ++] = (byte)(vlen >> k);
+                               }
+                               off ++;
+                       }
+               }
+
+               /*
+                * Encode value. We must adjust the start/end window to
+                * make it relative to the value.
+                */
+               EncodeValue(start - off, end - off, dst, dstOff);
+               off += vlen;
+
+               /*
+                * Compute copied length.
+                */
+               return Math.Max(0, Math.Min(off, end) - Math.Max(0, start));
+       }
+
+       /*
+        * Encode the value into the provided buffer. Only value bytes
+        * at offsets between 'start' (inclusive) and 'end' (exclusive)
+        * are written. Actual number of written bytes is returned.
+        * Offsets are relative to the start of the value.
+        */
+       int EncodeValue(int start, int end, byte[] dst, int dstOff)
+       {
+               int orig = dstOff;
+               if (objBuf == null) {
+                       int k = 0;
+                       foreach (AsnElt a in Sub) {
+                               int slen = a.EncodedLength;
+                               dstOff += a.Encode(start - k, end - k,
+                                       dst, dstOff);
+                               k += slen;
+                       }
+               } else {
+                       int from = Math.Max(0, start);
+                       int to = Math.Min(valLen, end);
+                       int len = to - from;
+                       if (len > 0) {
+                               Array.Copy(objBuf, valOff + from,
+                                       dst, dstOff, len);
+                               dstOff += len;
+                       }
+               }
+               return dstOff - orig;
+       }
+
+       /*
+        * Copy a value chunk. The provided offset ('off') and length ('len')
+        * define the chunk to copy; the offset is relative to the value
+        * start (first value byte has offset 0). If the requested window
+        * exceeds the value boundaries, an exception is thrown.
+        */
+       public void CopyValueChunk(int off, int len, byte[] dst, int dstOff)
+       {
+               int vlen = ValueLength;
+               if (off < 0 || len < 0 || len > (vlen - off)) {
+                       throw new AsnException(String.Format(
+                               "invalid value window {0}:{1}"
+                               + " (value length = {2})", off, len, vlen));
+               }
+               EncodeValue(off, off + len, dst, dstOff);
+       }
+
+       /*
+        * Copy the value into the specified array. The value length is
+        * returned.
+        */
+       public int CopyValue(byte[] dst, int off)
+       {
+               return EncodeValue(0, Int32.MaxValue, dst, off);
+       }
+
+       /*
+        * Get a copy of the value as a freshly allocated array.
+        */
+       public byte[] CopyValue()
+       {
+               byte[] r = new byte[ValueLength];
+               EncodeValue(0, r.Length, r, 0);
+               return r;
+       }
+
+       /*
+        * Get the value. This may return a shared buffer, that MUST NOT
+        * be modified.
+        */
+       byte[] GetValue(out int off, out int len)
+       {
+               if (objBuf == null) {
+                       /*
+                        * We can modify objBuf because CopyValue()
+                        * called ValueLength, thus valLen has been
+                        * filled.
+                        */
+                       objBuf = CopyValue();
+                       off = 0;
+                       len = objBuf.Length;
+               } else {
+                       off = valOff;
+                       len = valLen;
+               }
+               return objBuf;
+       }
+
+       /*
+        * Interpret the value as a BOOLEAN.
+        */
+       public bool GetBoolean()
+       {
+               if (Constructed) {
+                       throw new AsnException(
+                               "invalid BOOLEAN (constructed)");
+               }
+               int vlen = ValueLength;
+               if (vlen != 1) {
+                       throw new AsnException(String.Format(
+                               "invalid BOOLEAN (length = {0})", vlen));
+               }
+               return ValueByte(0) != 0;
+       }
+
+       /*
+        * Interpret the value as an INTEGER. An exception is thrown if
+        * the value does not fit in a 'long'.
+        */
+       public long GetInteger()
+       {
+               if (Constructed) {
+                       throw new AsnException(
+                               "invalid INTEGER (constructed)");
+               }
+               int vlen = ValueLength;
+               if (vlen == 0) {
+                       throw new AsnException("invalid INTEGER (length = 0)");
+               }
+               int v = ValueByte(0);
+               long x;
+               if ((v & 0x80) != 0) {
+                       x = -1;
+                       for (int k = 0; k < vlen; k ++) {
+                               if (x < ((-1L) << 55)) {
+                                       throw new AsnException(
+                                               "integer overflow (negative)");
+                               }
+                               x = (x << 8) + (long)ValueByte(k);
+                       }
+               } else {
+                       x = 0;
+                       for (int k = 0; k < vlen; k ++) {
+                               if (x >= (1L << 55)) {
+                                       throw new AsnException(
+                                               "integer overflow (positive)");
+                               }
+                               x = (x << 8) + (long)ValueByte(k);
+                       }
+               }
+               return x;
+       }
+
+       /*
+        * Interpret the value as an INTEGER. An exception is thrown if
+        * the value is outside of the provided range.
+        */
+       public long GetInteger(long min, long max)
+       {
+               long v = GetInteger();
+               if (v < min || v > max) {
+                       throw new AsnException("integer out of allowed range");
+               }
+               return v;
+       }
+
+       /*
+        * Interpret the value as an INTEGER. Return its hexadecimal
+        * representation (uppercase), preceded by a '0x' or '-0x'
+        * header, depending on the integer sign. The number of
+        * hexadecimal digits is even. Leading zeroes are returned (but
+        * one may remain, to ensure an even number of digits). If the
+        * integer has value 0, then 0x00 is returned.
+        */
+       public string GetIntegerHex()
+       {
+               if (Constructed) {
+                       throw new AsnException(
+                               "invalid INTEGER (constructed)");
+               }
+               int vlen = ValueLength;
+               if (vlen == 0) {
+                       throw new AsnException("invalid INTEGER (length = 0)");
+               }
+               StringBuilder sb = new StringBuilder();
+               byte[] tmp = CopyValue();
+               if (tmp[0] >= 0x80) {
+                       sb.Append('-');
+                       int cc = 1;
+                       for (int i = tmp.Length - 1; i >= 0; i --) {
+                               int v = ((~tmp[i]) & 0xFF) + cc;
+                               tmp[i] = (byte)v;
+                               cc = v >> 8;
+                       }
+               }
+               int k = 0;
+               while (k < tmp.Length && tmp[k] == 0) {
+                       k ++;
+               }
+               if (k == tmp.Length) {
+                       return "0x00";
+               }
+               sb.Append("0x");
+               while (k < tmp.Length) {
+                       sb.AppendFormat("{0:X2}", tmp[k ++]);
+               }
+               return sb.ToString();
+       }
+
+       /*
+        * Interpret the value as an OCTET STRING. The value bytes are
+        * returned. This method supports constructed values and performs
+        * the reassembly.
+        */
+       public byte[] GetOctetString()
+       {
+               int len = GetOctetString(null, 0);
+               byte[] r = new byte[len];
+               GetOctetString(r, 0);
+               return r;
+       }
+
+       /*
+        * Interpret the value as an OCTET STRING. The value bytes are
+        * written in dst[], starting at offset 'off', and the total value
+        * length is returned. If 'dst' is null, then no byte is written
+        * anywhere, but the total length is still returned. This method
+        * supports constructed values and performs the reassembly.
+        */
+       public int GetOctetString(byte[] dst, int off)
+       {
+               if (Constructed) {
+                       int orig = off;
+                       foreach (AsnElt ae in Sub) {
+                               ae.CheckTag(AsnElt.OCTET_STRING);
+                               off += ae.GetOctetString(dst, off);
+                       }
+                       return off - orig;
+               }
+               if (dst != null) {
+                       return CopyValue(dst, off);
+               } else {
+                       return ValueLength;
+               }
+       }
+
+       /*
+        * Interpret the value as a BIT STRING. The bits are returned,
+        * with the "ignored bits" cleared.
+        */
+       public byte[] GetBitString()
+       {
+               int bitLength;
+               return GetBitString(out bitLength);
+       }
+
+       /*
+        * Interpret the value as a BIT STRING. The bits are returned,
+        * with the "ignored bits" cleared. The actual bit length is
+        * written in 'bitLength'.
+        */
+       public byte[] GetBitString(out int bitLength)
+       {
+               if (Constructed) {
+                       /*
+                        * TODO: support constructed BIT STRING values.
+                        */
+                       throw new AsnException(
+                               "invalid BIT STRING (constructed)");
+               }
+               int vlen = ValueLength;
+               if (vlen == 0) {
+                       throw new AsnException(
+                               "invalid BIT STRING (length = 0)");
+               }
+               int fb = ValueByte(0);
+               if (fb > 7 || (vlen == 1 && fb != 0)) {
+                       throw new AsnException(String.Format(
+                               "invalid BIT STRING (start = 0x{0:X2})", fb));
+               }
+               byte[] r = new byte[vlen - 1];
+               CopyValueChunk(1, vlen - 1, r, 0);
+               if (vlen > 1) {
+                       r[r.Length - 1] &= (byte)(0xFF << fb);
+               }
+               bitLength = (r.Length << 3) - fb;
+               return r;
+       }
+
+       /*
+        * Interpret the value as a NULL.
+        */
+       public void CheckNull()
+       {
+               if (Constructed) {
+                       throw new AsnException(
+                               "invalid NULL (constructed)");
+               }
+               if (ValueLength != 0) {
+                       throw new AsnException(String.Format(
+                               "invalid NULL (length = {0})", ValueLength));
+               }
+       }
+
+       /*
+        * Interpret the value as an OBJECT IDENTIFIER, and return it
+        * (in decimal-dotted string format).
+        */
+       public string GetOID()
+       {
+               CheckPrimitive();
+               if (valLen == 0) {
+                       throw new AsnException("zero-length OID");
+               }
+               int v = objBuf[valOff];
+               if (v >= 120) {
+                       throw new AsnException(
+                               "invalid OID: first byte = " + v);
+               }
+               StringBuilder sb = new StringBuilder();
+               sb.Append(v / 40);
+               sb.Append('.');
+               sb.Append(v % 40);
+               long acc = 0;
+               bool uv = false;
+               for (int i = 1; i < valLen; i ++) {
+                       v = objBuf[valOff + i];
+                       if ((acc >> 56) != 0) {
+                               throw new AsnException(
+                                       "invalid OID: integer overflow");
+                       }
+                       acc = (acc << 7) + (long)(v & 0x7F);
+                       if ((v & 0x80) == 0) {
+                               sb.Append('.');
+                               sb.Append(acc);
+                               acc = 0;
+                               uv = false;
+                       } else {
+                               uv = true;
+                       }
+               }
+               if (uv) {
+                       throw new AsnException(
+                               "invalid OID: truncated");
+               }
+               return sb.ToString();
+       }
+
+       /*
+        * Get the object value as a string. The string type is inferred
+        * from the tag.
+        */
+       public string GetString()
+       {
+               if (TagClass != UNIVERSAL) {
+                       throw new AsnException(String.Format(
+                               "cannot infer string type: {0}:{1}",
+                               TagClass, TagValue));
+               }
+               return GetString(TagValue);
+       }
+
+       /*
+        * Get the object value as a string. The string type is provided
+        * (universal tag value). Supported string types include
+        * NumericString, PrintableString, IA5String, TeletexString
+        * (interpreted as ISO-8859-1), UTF8String, BMPString and
+        * UniversalString; the "time types" (UTCTime and GeneralizedTime)
+        * are also supported, though, in their case, the internal
+        * contents are not checked (they are decoded as PrintableString).
+        */
+       public string GetString(int type)
+       {
+               if (Constructed) {
+                       throw new AsnException(
+                               "invalid string (constructed)");
+               }
+               switch (type) {
+               case NumericString:
+               case PrintableString:
+               case IA5String:
+               case TeletexString:
+               case UTCTime:
+               case GeneralizedTime:
+                       return DecodeMono(objBuf, valOff, valLen, type);
+               case UTF8String:
+                       return DecodeUTF8(objBuf, valOff, valLen);
+               case BMPString:
+                       return DecodeUTF16(objBuf, valOff, valLen);
+               case UniversalString:
+                       return DecodeUTF32(objBuf, valOff, valLen);
+               default:
+                       throw new AsnException(
+                               "unsupported string type: " + type);
+               }
+       }
+
+       static string DecodeMono(byte[] buf, int off, int len, int type)
+       {
+               char[] tc = new char[len];
+               for (int i = 0; i < len; i ++) {
+                       tc[i] = (char)buf[off + i];
+               }
+               VerifyChars(tc, type);
+               return new string(tc);
+       }
+
+       static string DecodeUTF8(byte[] buf, int off, int len)
+       {
+               /*
+                * Skip BOM.
+                */
+               if (len >= 3 && buf[off] == 0xEF
+                       && buf[off + 1] == 0xBB && buf[off + 2] == 0xBF)
+               {
+                       off += 3;
+                       len -= 3;
+               }
+               char[] tc = null;
+               for (int k = 0; k < 2; k ++) {
+                       int tcOff = 0;
+                       for (int i = 0; i < len; i ++) {
+                               int c = buf[off + i];
+                               int e;
+                               if (c < 0x80) {
+                                       e = 0;
+                               } else if (c < 0xC0) {
+                                       throw BadByte(c, UTF8String);
+                               } else if (c < 0xE0) {
+                                       c &= 0x1F;
+                                       e = 1;
+                               } else if (c < 0xF0) {
+                                       c &= 0x0F;
+                                       e = 2;
+                               } else if (c < 0xF8) {
+                                       c &= 0x07;
+                                       e = 3;
+                               } else {
+                                       throw BadByte(c, UTF8String);
+                               }
+                               while (e -- > 0) {
+                                       if (++ i >= len) {
+                                               throw new AsnException(
+                                                       "invalid UTF-8 string");
+                                       }
+                                       int d = buf[off + i];
+                                       if (d < 0x80 || d > 0xBF) {
+                                               throw BadByte(d, UTF8String);
+                                       }
+                                       c = (c << 6) + (d & 0x3F);
+                               }
+                               if (c > 0x10FFFF) {
+                                       throw BadChar(c, UTF8String);
+                               }
+                               if (c > 0xFFFF) {
+                                       c -= 0x10000;
+                                       int hi = 0xD800 + (c >> 10);
+                                       int lo = 0xDC00 + (c & 0x3FF);
+                                       if (tc != null) {
+                                               tc[tcOff] = (char)hi;
+                                               tc[tcOff + 1] = (char)lo;
+                                       }
+                                       tcOff += 2;
+                               } else {
+                                       if (tc != null) {
+                                               tc[tcOff] = (char)c;
+                                       }
+                                       tcOff ++;
+                               }
+                       }
+                       if (tc == null) {
+                               tc = new char[tcOff];
+                       }
+               }
+               VerifyChars(tc, UTF8String);
+               return new string(tc);
+       }
+
+       static string DecodeUTF16(byte[] buf, int off, int len)
+       {
+               if ((len & 1) != 0) {
+                       throw new AsnException(
+                               "invalid UTF-16 string: length = " + len);
+               }
+               len >>= 1;
+               if (len == 0) {
+                       return "";
+               }
+               bool be = true;
+               int hi = buf[off];
+               int lo = buf[off + 1];
+               if (hi == 0xFE && lo == 0xFF) {
+                       off += 2;
+                       len --;
+               } else if (hi == 0xFF && lo == 0xFE) {
+                       off += 2;
+                       len --;
+                       be = false;
+               }
+               char[] tc = new char[len];
+               for (int i = 0; i < len; i ++) {
+                       int b0 = buf[off ++];
+                       int b1 = buf[off ++];
+                       if (be) {
+                               tc[i] = (char)((b0 << 8) + b1);
+                       } else {
+                               tc[i] = (char)((b1 << 8) + b0);
+                       }
+               }
+               VerifyChars(tc, BMPString);
+               return new string(tc);
+       }
+
+       static string DecodeUTF32(byte[] buf, int off, int len)
+       {
+               if ((len & 3) != 0) {
+                       throw new AsnException(
+                               "invalid UTF-32 string: length = " + len);
+               }
+               len >>= 2;
+               if (len == 0) {
+                       return "";
+               }
+               bool be = true;
+               if (buf[off] == 0x00
+                       && buf[off + 1] == 0x00
+                       && buf[off + 2] == 0xFE
+                       && buf[off + 3] == 0xFF)
+               {
+                       off += 4;
+                       len --;
+               } else if (buf[off] == 0xFF
+                       && buf[off + 1] == 0xFE
+                       && buf[off + 2] == 0x00
+                       && buf[off + 3] == 0x00)
+               {
+                       off += 4;
+                       len --;
+                       be = false;
+               }
+
+               char[] tc = null;
+               for (int k = 0; k < 2; k ++) {
+                       int tcOff = 0;
+                       for (int i = 0; i < len; i ++) {
+                               uint b0 = buf[off + 0];
+                               uint b1 = buf[off + 1];
+                               uint b2 = buf[off + 2];
+                               uint b3 = buf[off + 3];
+                               uint c;
+                               if (be) {
+                                       c = (b0 << 24) | (b1 << 16)
+                                               | (b2 << 8) | b3;
+                               } else {
+                                       c = (b3 << 24) | (b2 << 16)
+                                               | (b1 << 8) | b0;
+                               }
+                               if (c > 0x10FFFF) {
+                                       throw BadChar((int)c, UniversalString);
+                               }
+                               if (c > 0xFFFF) {
+                                       c -= 0x10000;
+                                       int hi = 0xD800 + (int)(c >> 10);
+                                       int lo = 0xDC00 + (int)(c & 0x3FF);
+                                       if (tc != null) {
+                                               tc[tcOff] = (char)hi;
+                                               tc[tcOff + 1] = (char)lo;
+                                       }
+                                       tcOff += 2;
+                               } else {
+                                       if (tc != null) {
+                                               tc[tcOff] = (char)c;
+                                       }
+                                       tcOff ++;
+                               }
+                       }
+                       if (tc == null) {
+                               tc = new char[tcOff];
+                       }
+               }
+               VerifyChars(tc, UniversalString);
+               return new string(tc);
+       }
+
+       static void VerifyChars(char[] tc, int type)
+       {
+               switch (type) {
+               case NumericString:
+                       foreach (char c in tc) {
+                               if (!IsNum(c)) {
+                                       throw BadChar(c, type);
+                               }
+                       }
+                       return;
+               case PrintableString:
+               case UTCTime:
+               case GeneralizedTime:
+                       foreach (char c in tc) {
+                               if (!IsPrintable(c)) {
+                                       throw BadChar(c, type);
+                               }
+                       }
+                       return;
+               case IA5String:
+                       foreach (char c in tc) {
+                               if (!IsIA5(c)) {
+                                       throw BadChar(c, type);
+                               }
+                       }
+                       return;
+               case TeletexString:
+                       foreach (char c in tc) {
+                               if (!IsLatin1(c)) {
+                                       throw BadChar(c, type);
+                               }
+                       }
+                       return;
+               }
+
+               /*
+                * For Unicode string types (UTF-8, BMP...).
+                */
+               for (int i = 0; i < tc.Length; i ++) {
+                       int c = tc[i];
+                       if (c >= 0xFDD0 && c <= 0xFDEF) {
+                               throw BadChar(c, type);
+                       }
+                       if (c == 0xFFFE || c == 0xFFFF) {
+                               throw BadChar(c, type);
+                       }
+                       if (c < 0xD800 || c > 0xDFFF) {
+                               continue;
+                       }
+                       if (c > 0xDBFF) {
+                               throw BadChar(c, type);
+                       }
+                       int hi = c & 0x3FF;
+                       if (++ i >= tc.Length) {
+                               throw BadChar(c, type);
+                       }
+                       c = tc[i];
+                       if (c < 0xDC00 || c > 0xDFFF) {
+                               throw BadChar(c, type);
+                       }
+                       int lo = c & 0x3FF;
+                       c = 0x10000 + lo + (hi << 10);
+                       if ((c & 0xFFFE) == 0xFFFE) {
+                               throw BadChar(c, type);
+                       }
+               }
+       }
+
+       static bool IsNum(int c)
+       {
+               return c == ' ' || (c >= '0' && c <= '9');
+       }
+
+       internal static bool IsPrintable(int c)
+       {
+               if (c >= 'A' && c <= 'Z') {
+                       return true;
+               }
+               if (c >= 'a' && c <= 'z') {
+                       return true;
+               }
+               if (c >= '0' && c <= '9') {
+                       return true;
+               }
+               switch (c) {
+               case ' ': case '(': case ')': case '+':
+               case ',': case '-': case '.': case '/':
+               case ':': case '=': case '?': case '\'':
+                       return true;
+               default:
+                       return false;
+               }
+       }
+
+       static bool IsIA5(int c)
+       {
+               return c < 128;
+       }
+
+       static bool IsLatin1(int c)
+       {
+               return c < 256;
+       }
+
+       static AsnException BadByte(int c, int type)
+       {
+               return new AsnException(String.Format(
+                       "unexpected byte 0x{0:X2} in string of type {1}",
+                       c, type));
+       }
+
+       static AsnException BadChar(int c, int type)
+       {
+               return new AsnException(String.Format(
+                       "unexpected character U+{0:X4} in string of type {1}",
+                       c, type));
+       }
+
+       /*
+        * Decode the value as a date/time. Returned object is in UTC.
+        * Type of date is inferred from the tag value.
+        */
+       public DateTime GetTime()
+       {
+               if (TagClass != UNIVERSAL) {
+                       throw new AsnException(String.Format(
+                               "cannot infer date type: {0}:{1}",
+                               TagClass, TagValue));
+               }
+               return GetTime(TagValue);
+       }
+
+       /*
+        * Decode the value as a date/time. Returned object is in UTC.
+        * The time string type is provided as parameter (UTCTime or
+        * GeneralizedTime).
+        */
+       public DateTime GetTime(int type)
+       {
+               bool isGen = false;
+               switch (type) {
+               case UTCTime:
+                       break;
+               case GeneralizedTime:
+                       isGen = true;
+                       break;
+               default:
+                       throw new AsnException(
+                               "unsupported date type: " + type);
+               }
+               string s = GetString(type);
+               string orig = s;
+
+               /*
+                * UTCTime has format:
+                *    YYMMDDhhmm[ss](Z|(+|-)hhmm)
+                *
+                * GeneralizedTime has format:
+                *    YYYYMMDDhhmmss[.uu*][Z|(+|-)hhmm]
+                *
+                * Differences between the two types:
+                * -- UTCTime encodes year over two digits; GeneralizedTime
+                * uses four digits. UTCTime years map to 1950..2049 (00 is
+                * 2000).
+                * -- Seconds are optional with UTCTime, mandatory with
+                * GeneralizedTime.
+                * -- GeneralizedTime can have fractional seconds (optional).
+                * -- Time zone is optional for GeneralizedTime. However,
+                * a missing time zone means "local time" which depends on
+                * the locality, so this is discouraged.
+                *
+                * Some other notes:
+                * -- If there is a fractional second, then it must include
+                * at least one digit. This implementation processes the
+                * first three digits, and ignores the rest (if present).
+                * -- Time zone offset ranges from -23:59 to +23:59.
+                * -- The calendar computations are delegated to .NET's
+                * DateTime (and DateTimeOffset) so this implements a
+                * Gregorian calendar, even for dates before 1589. Year 0
+                * is not supported.
+                */
+
+               /*
+                * Check characters.
+                */
+               foreach (char c in s) {
+                       if (c >= '0' && c <= '9') {
+                               continue;
+                       }
+                       if (c == '.' || c == '+' || c == '-' || c == 'Z') {
+                               continue;
+                       }
+                       throw BadTime(type, orig);
+               }
+
+               bool good = true;
+
+               /*
+                * Parse the time zone.
+                */
+               int tzHours = 0;
+               int tzMinutes = 0;
+               bool negZ = false;
+               bool noTZ = false;
+               if (s.EndsWith("Z")) {
+                       s = s.Substring(0, s.Length - 1);
+               } else {
+                       int j = s.IndexOf('+');
+                       if (j < 0) {
+                               j = s.IndexOf('-');
+                               negZ = true;
+                       }
+                       if (j < 0) {
+                               noTZ = true;
+                       } else {
+                               string t = s.Substring(j + 1);
+                               s = s.Substring(0, j);
+                               if (t.Length != 4) {
+                                       throw BadTime(type, orig);
+                               }
+                               tzHours = Dec2(t, 0, ref good);
+                               tzMinutes = Dec2(t, 2, ref good);
+                               if (tzHours < 0 || tzHours > 23
+                                       || tzMinutes < 0 || tzMinutes > 59)
+                               {
+                                       throw BadTime(type, orig);
+                               }
+                       }
+               }
+
+               /*
+                * Lack of time zone is allowed only for GeneralizedTime.
+                */
+               if (noTZ && !isGen) {
+                       throw BadTime(type, orig);
+               }
+
+               /*
+                * Parse the date elements.
+                */
+               if (s.Length < 4) {
+                       throw BadTime(type, orig);
+               }
+               int year = Dec2(s, 0, ref good);
+               if (isGen) {
+                       year = year * 100 + Dec2(s, 2, ref good);
+                       s = s.Substring(4);
+               } else {
+                       if (year < 50) {
+                               year += 100;
+                       }
+                       year += 1900;
+                       s = s.Substring(2);
+               }
+               int month = Dec2(s, 0, ref good);
+               int day = Dec2(s, 2, ref good);
+               int hour = Dec2(s, 4, ref good);
+               int minute = Dec2(s, 6, ref good);
+               int second = 0;
+               int millisecond = 0;
+               if (isGen) {
+                       second = Dec2(s, 8, ref good);
+                       if (s.Length >= 12 && s[10] == '.') {
+                               s = s.Substring(11);
+                               foreach (char c in s) {
+                                       if (c < '0' || c > '9') {
+                                               good = false;
+                                               break;
+                                       }
+                               }
+                               s += "0000";
+                               millisecond = 10 * Dec2(s, 0, ref good)
+                                       + Dec2(s, 2, ref good) / 10;
+                       } else if (s.Length != 10) {
+                               good = false;
+                       }
+               } else {
+                       switch (s.Length) {
+                       case 8:
+                               break;
+                       case 10:
+                               second = Dec2(s, 8, ref good);
+                               break;
+                       default:
+                               throw BadTime(type, orig);
+                       }
+               }
+
+               /*
+                * Parsing is finished; if any error occurred, then
+                * the 'good' flag has been cleared.
+                */
+               if (!good) {
+                       throw BadTime(type, orig);
+               }
+
+               /*
+                * Leap seconds are not supported by .NET, so we claim
+                * they do not occur.
+                */
+               if (second == 60) {
+                       second = 59;
+               }
+
+               /*
+                * .NET implementation performs all the checks (including
+                * checks on month length depending on year, as per the
+                * proleptic Gregorian calendar).
+                */
+               try {
+                       if (noTZ) {
+                               DateTime dt = new DateTime(year, month, day,
+                                       hour, minute, second, millisecond,
+                                       DateTimeKind.Local);
+                               return dt.ToUniversalTime();
+                       }
+                       TimeSpan tzOff = new TimeSpan(tzHours, tzMinutes, 0);
+                       if (negZ) {
+                               tzOff = tzOff.Negate();
+                       }
+                       DateTimeOffset dto = new DateTimeOffset(
+                               year, month, day, hour, minute, second,
+                               millisecond, tzOff);
+                       return dto.UtcDateTime;
+               } catch (Exception e) {
+                       throw BadTime(type, orig, e);
+               }
+       }
+
+       static int Dec2(string s, int off, ref bool good)
+       {
+               if (off < 0 || off >= (s.Length - 1)) {
+                       good = false;
+                       return -1;
+               }
+               char c1 = s[off];
+               char c2 = s[off + 1];
+               if (c1 < '0' || c1 > '9' || c2 < '0' || c2 > '9') {
+                       good = false;
+                       return -1;
+               }
+               return 10 * (c1 - '0') + (c2 - '0');
+       }
+
+       static AsnException BadTime(int type, string s)
+       {
+               return BadTime(type, s, null);
+       }
+
+       static AsnException BadTime(int type, string s, Exception e)
+       {
+               string tt = (type == UTCTime) ? "UTCTime" : "GeneralizedTime";
+               string msg = String.Format("invalid {0} string: '{1}'", tt, s);
+               if (e == null) {
+                       return new AsnException(msg);
+               } else {
+                       return new AsnException(msg, e);
+               }
+       }
+
+       /* =============================================================== */
+
+       /*
+        * Create a new element for a primitive value. The provided buffer
+        * is internally copied.
+        */
+       public static AsnElt MakePrimitive(int tagValue, byte[] val)
+       {
+               return MakePrimitive(UNIVERSAL, tagValue, val, 0, val.Length);
+       }
+
+       /*
+        * Create a new element for a primitive value. The provided buffer
+        * is internally copied.
+        */
+       public static AsnElt MakePrimitive(int tagValue,
+               byte[] val, int off, int len)
+       {
+               return MakePrimitive(UNIVERSAL, tagValue, val, off, len);
+       }
+
+       /*
+        * Create a new element for a primitive value. The provided buffer
+        * is internally copied.
+        */
+       public static AsnElt MakePrimitive(
+               int tagClass, int tagValue, byte[] val)
+       {
+               return MakePrimitive(tagClass, tagValue, val, 0, val.Length);
+       }
+
+       /*
+        * Create a new element for a primitive value. The provided buffer
+        * is internally copied.
+        */
+       public static AsnElt MakePrimitive(int tagClass, int tagValue,
+               byte[] val, int off, int len)
+       {
+               byte[] nval = new byte[len];
+               Array.Copy(val, off, nval, 0, len);
+               return MakePrimitiveInner(tagClass, tagValue, nval, 0, len);
+       }
+
+       /*
+        * Like MakePrimitive(), but the provided array is NOT copied.
+        * This is for other factory methods that already allocate a
+        * new array.
+        */
+       static AsnElt MakePrimitiveInner(int tagValue, byte[] val)
+       {
+               return MakePrimitiveInner(UNIVERSAL, tagValue,
+                       val, 0, val.Length);
+       }
+
+       static AsnElt MakePrimitiveInner(int tagValue,
+               byte[] val, int off, int len)
+       {
+               return MakePrimitiveInner(UNIVERSAL, tagValue, val, off, len);
+       }
+
+       static AsnElt MakePrimitiveInner(int tagClass, int tagValue, byte[] val)
+       {
+               return MakePrimitiveInner(tagClass, tagValue,
+                       val, 0, val.Length);
+       }
+
+       static AsnElt MakePrimitiveInner(int tagClass, int tagValue,
+               byte[] val, int off, int len)
+       {
+               AsnElt a = new AsnElt();
+               a.objBuf = new byte[len];
+               Array.Copy(val, off, a.objBuf, 0, len);
+               a.objOff = 0;
+               a.objLen = -1;
+               a.valOff = 0;
+               a.valLen = len;
+               a.hasEncodedHeader = false;
+               if (tagClass < 0 || tagClass > 3) {
+                       throw new AsnException(
+                               "invalid tag class: " + tagClass);
+               }
+               if (tagValue < 0) {
+                       throw new AsnException(
+                               "invalid tag value: " + tagValue);
+               }
+               a.TagClass = tagClass;
+               a.TagValue = tagValue;
+               a.Sub = null;
+               return a;
+       }
+
+       /*
+        * Create a new INTEGER value for the provided integer.
+        */
+       public static AsnElt MakeInteger(long x)
+       {
+               if (x >= 0) {
+                       return MakeInteger((ulong)x);
+               }
+               int k = 1;
+               for (long w = x; w <= -(long)0x80; w >>= 8) {
+                       k ++;
+               }
+               byte[] v = new byte[k];
+               for (long w = x; k > 0; w >>= 8) {
+                       v[-- k] = (byte)w;
+               }
+               return MakePrimitiveInner(INTEGER, v);
+       }
+
+       /*
+        * Create a new INTEGER value for the provided integer.
+        */
+       public static AsnElt MakeInteger(ulong x)
+       {
+               int k = 1;
+               for (ulong w = x; w >= 0x80; w >>= 8) {
+                       k ++;
+               }
+               byte[] v = new byte[k];
+               for (ulong w = x; k > 0; w >>= 8) {
+                       v[-- k] = (byte)w;
+               }
+               return MakePrimitiveInner(INTEGER, v);
+       }
+
+       /*
+        * Create a new INTEGER value for the provided integer. The x[]
+        * array uses _unsigned_ big-endian encoding.
+        */
+       public static AsnElt MakeInteger(byte[] x)
+       {
+               int xLen = x.Length;
+               int j = 0;
+               while (j < xLen && x[j] == 0x00) {
+                       j ++;
+               }
+               if (j == xLen) {
+                       return MakePrimitiveInner(INTEGER, new byte[] { 0x00 });
+               }
+               byte[] v;
+               if (x[j] < 0x80) {
+                       v = new byte[xLen - j];
+                       Array.Copy(x, j, v, 0, v.Length);
+               } else {
+                       v = new byte[1 + xLen - j];
+                       Array.Copy(x, j, v, 1, v.Length - 1);
+               }
+               return MakePrimitiveInner(INTEGER, v);
+       }
+
+       /*
+        * Create a new INTEGER value for the provided integer. The x[]
+        * array uses _signed_ big-endian encoding.
+        */
+       public static AsnElt MakeIntegerSigned(byte[] x)
+       {
+               int xLen = x.Length;
+               if (xLen == 0) {
+                       throw new AsnException(
+                               "Invalid signed integer (empty)");
+               }
+               int j = 0;
+               if (x[0] >= 0x80) {
+                       while (j < (xLen - 1)
+                               && x[j] == 0xFF
+                               && x[j + 1] >= 0x80)
+                       {
+                               j ++;
+                       }
+               } else {
+                       while (j < (xLen - 1)
+                               && x[j] == 0x00
+                               && x[j + 1] < 0x80)
+                       {
+                               j ++;
+                       }
+               }
+               byte[] v = new byte[xLen - j];
+               Array.Copy(x, j, v, 0, v.Length);
+               return MakePrimitiveInner(INTEGER, v);
+       }
+
+       /*
+        * Create a BIT STRING from the provided value. The number of
+        * "unused bits" is set to 0.
+        */
+       public static AsnElt MakeBitString(byte[] buf)
+       {
+               return MakeBitString(buf, 0, buf.Length);
+       }
+
+       public static AsnElt MakeBitString(byte[] buf, int off, int len)
+       {
+               byte[] tmp = new byte[len + 1];
+               Array.Copy(buf, off, tmp, 1, len);
+               return MakePrimitiveInner(BIT_STRING, tmp);
+       }
+
+       /*
+        * Create a BIT STRING from the provided value. The number of
+        * "unused bits" is specified.
+        */
+       public static AsnElt MakeBitString(int unusedBits, byte[] buf)
+       {
+               return MakeBitString(unusedBits, buf, 0, buf.Length);
+       }
+
+       public static AsnElt MakeBitString(int unusedBits,
+               byte[] buf, int off, int len)
+       {
+               if (unusedBits < 0 || unusedBits > 7
+                       || (unusedBits != 0 && len == 0))
+               {
+                       throw new AsnException(
+                               "Invalid number of unused bits in BIT STRING: "
+                               + unusedBits);
+               }
+               byte[] tmp = new byte[len + 1];
+               tmp[0] = (byte)unusedBits;
+               Array.Copy(buf, off, tmp, 1, len);
+               if (len > 0) {
+                       tmp[len - 1] &= (byte)(0xFF << unusedBits);
+               }
+               return MakePrimitiveInner(BIT_STRING, tmp);
+       }
+
+       /*
+        * Create an OCTET STRING from the provided value.
+        */
+       public static AsnElt MakeBlob(byte[] buf)
+       {
+               return MakeBlob(buf, 0, buf.Length);
+       }
+
+       public static AsnElt MakeBlob(byte[] buf, int off, int len)
+       {
+               return MakePrimitive(OCTET_STRING, buf, off, len);
+       }
+
+       /*
+        * Create a new constructed elements, by providing the relevant
+        * sub-elements.
+        */
+       public static AsnElt Make(int tagValue, params AsnElt[] subs)
+       {
+               return Make(UNIVERSAL, tagValue, subs);
+       }
+
+       /*
+        * Create a new constructed elements, by providing the relevant
+        * sub-elements.
+        */
+       public static AsnElt Make(int tagClass, int tagValue,
+               params AsnElt[] subs)
+       {
+               AsnElt a = new AsnElt();
+               a.objBuf = null;
+               a.objOff = 0;
+               a.objLen = -1;
+               a.valOff = 0;
+               a.valLen = -1;
+               a.hasEncodedHeader = false;
+               if (tagClass < 0 || tagClass > 3) {
+                       throw new AsnException(
+                               "invalid tag class: " + tagClass);
+               }
+               if (tagValue < 0) {
+                       throw new AsnException(
+                               "invalid tag value: " + tagValue);
+               }
+               a.TagClass = tagClass;
+               a.TagValue = tagValue;
+               if (subs == null) {
+                       a.Sub = new AsnElt[0];
+               } else {
+                       a.Sub = new AsnElt[subs.Length];
+                       Array.Copy(subs, 0, a.Sub, 0, subs.Length);
+               }
+               return a;
+       }
+
+       /*
+        * Create a SET OF: sub-elements are automatically sorted by
+        * lexicographic order of their DER encodings. Identical elements
+        * are merged.
+        */
+       public static AsnElt MakeSetOf(params AsnElt[] subs)
+       {
+               AsnElt a = new AsnElt();
+               a.objBuf = null;
+               a.objOff = 0;
+               a.objLen = -1;
+               a.valOff = 0;
+               a.valLen = -1;
+               a.hasEncodedHeader = false;
+               a.TagClass = UNIVERSAL;
+               a.TagValue = SET;
+               if (subs == null) {
+                       a.Sub = new AsnElt[0];
+               } else {
+                       SortedDictionary<byte[], AsnElt> d =
+                               new SortedDictionary<byte[], AsnElt>(
+                                       COMPARER_LEXICOGRAPHIC);
+                       foreach (AsnElt ax in subs) {
+                               d[ax.Encode()] = ax;
+                       }
+                       AsnElt[] tmp = new AsnElt[d.Count];
+                       int j = 0;
+                       foreach (AsnElt ax in d.Values) {
+                               tmp[j ++] = ax;
+                       }
+                       a.Sub = tmp;
+               }
+               return a;
+       }
+
+       static IComparer<byte[]> COMPARER_LEXICOGRAPHIC =
+               new ComparerLexicographic();
+
+       class ComparerLexicographic : IComparer<byte[]> {
+
+               public int Compare(byte[] x, byte[] y)
+               {
+                       int xLen = x.Length;
+                       int yLen = y.Length;
+                       int cLen = Math.Min(xLen, yLen);
+                       for (int i = 0; i < cLen; i ++) {
+                               if (x[i] != y[i]) {
+                                       return (int)x[i] - (int)y[i];
+                               }
+                       }
+                       return xLen - yLen;
+               }
+       }
+
+       /*
+        * Wrap an element into an explicit tag.
+        */
+       public static AsnElt MakeExplicit(int tagClass, int tagValue, AsnElt x)
+       {
+               return Make(tagClass, tagValue, x);
+       }
+
+       /*
+        * Wrap an element into an explicit CONTEXT tag.
+        */
+       public static AsnElt MakeExplicit(int tagValue, AsnElt x)
+       {
+               return Make(CONTEXT, tagValue, x);
+       }
+
+       /*
+        * Apply an implicit tag to a value. The source AsnElt object
+        * is unmodified; a new object is returned.
+        */
+       public static AsnElt MakeImplicit(int tagClass, int tagValue, AsnElt x)
+       {
+               if (x.Constructed) {
+                       return Make(tagClass, tagValue, x.Sub);
+               }
+               AsnElt a = new AsnElt();
+               a.objBuf = x.GetValue(out a.valOff, out a.valLen);
+               a.objOff = 0;
+               a.objLen = -1;
+               a.hasEncodedHeader = false;
+               a.TagClass = tagClass;
+               a.TagValue = tagValue;
+               a.Sub = null;
+               return a;
+       }
+
+       public static AsnElt NULL_V = AsnElt.MakePrimitive(
+               NULL, new byte[0]);
+
+       public static AsnElt BOOL_TRUE = AsnElt.MakePrimitive(
+               BOOLEAN, new byte[] { 0xFF });
+       public static AsnElt BOOL_FALSE = AsnElt.MakePrimitive(
+               BOOLEAN, new byte[] { 0x00 });
+
+       /*
+        * Create an OBJECT IDENTIFIER from its string representation.
+        * This function tolerates extra leading zeros.
+        */
+       public static AsnElt MakeOID(string str)
+       {
+               List<long> r = new List<long>();
+               int n = str.Length;
+               long x = -1;
+               for (int i = 0; i < n; i ++) {
+                       int c = str[i];
+                       if (c == '.') {
+                               if (x < 0) {
+                                       throw new AsnException(
+                                               "invalid OID (empty element)");
+                               }
+                               r.Add(x);
+                               x = -1;
+                               continue;
+                       }
+                       if (c < '0' || c > '9') {
+                               throw new AsnException(String.Format(
+                                       "invalid character U+{0:X4} in OID",
+                                       c));
+                       }
+                       if (x < 0) {
+                               x = 0;
+                       } else if (x > ((Int64.MaxValue - 9) / 10)) {
+                               throw new AsnException("OID element overflow");
+                       }
+                       x = x * (long)10 + (long)(c - '0');
+               }
+               if (x < 0) {
+                       throw new AsnException(
+                               "invalid OID (empty element)");
+               }
+               r.Add(x);
+               if (r.Count < 2) {
+                       throw new AsnException(
+                               "invalid OID (not enough elements)");
+               }
+               if (r[0] > 2 || r[1] > 40) {
+                       throw new AsnException(
+                               "invalid OID (first elements out of range)");
+               }
+
+               MemoryStream ms = new MemoryStream();
+               ms.WriteByte((byte)(40 * (int)r[0] + (int)r[1]));
+               for (int i = 2; i < r.Count; i ++) {
+                       long v = r[i];
+                       if (v < 0x80) {
+                               ms.WriteByte((byte)v);
+                               continue;
+                       }
+                       int k = -7;
+                       for (long w = v; w != 0; w >>= 7, k += 7);
+                       ms.WriteByte((byte)(0x80 + (int)(v >> k)));
+                       for (k -= 7; k >= 0; k -= 7) {
+                               int z = (int)(v >> k) & 0x7F;
+                               if (k > 0) {
+                                       z |= 0x80;
+                               }
+                               ms.WriteByte((byte)z);
+                       }
+               }
+               byte[] buf = ms.ToArray();
+               return MakePrimitiveInner(OBJECT_IDENTIFIER,
+                       buf, 0, buf.Length);
+       }
+
+       /*
+        * Create a string of the provided type and contents. The string
+        * type is a universal tag value for one of the string or time
+        * types.
+        */
+       public static AsnElt MakeString(int type, string str)
+       {
+               VerifyChars(str.ToCharArray(), type);
+               byte[] buf;
+               switch (type) {
+               case NumericString:
+               case PrintableString:
+               case UTCTime:
+               case GeneralizedTime:
+               case IA5String:
+               case TeletexString:
+                       buf = EncodeMono(str);
+                       break;
+               case UTF8String:
+                       buf = EncodeUTF8(str);
+                       break;
+               case BMPString:
+                       buf = EncodeUTF16(str);
+                       break;
+               case UniversalString:
+                       buf = EncodeUTF32(str);
+                       break;
+               default:
+                       throw new AsnException(
+                               "unsupported string type: " + type);
+               }
+               return MakePrimitiveInner(type, buf);
+       }
+
+       static byte[] EncodeMono(string str)
+       {
+               byte[] r = new byte[str.Length];
+               int k = 0;
+               foreach (char c in str) {
+                       r[k ++] = (byte)c;
+               }
+               return r;
+       }
+
+       /*
+        * Get the code point at offset 'off' in the string. Either one
+        * or two 'char' slots are used; 'off' is updated accordingly.
+        */
+       static int CodePoint(string str, ref int off)
+       {
+               int c = str[off ++];
+               if (c >= 0xD800 && c < 0xDC00 && off < str.Length) {
+                       int d = str[off];
+                       if (d >= 0xDC00 && d < 0xE000) {
+                               c = ((c & 0x3FF) << 10)
+                                       + (d & 0x3FF) + 0x10000;
+                               off ++;
+                       }
+               }
+               return c;
+       }
+
+       static byte[] EncodeUTF8(string str)
+       {
+               int k = 0;
+               int n = str.Length;
+               MemoryStream ms = new MemoryStream();
+               while (k < n) {
+                       int cp = CodePoint(str, ref k);
+                       if (cp < 0x80) {
+                               ms.WriteByte((byte)cp);
+                       } else if (cp < 0x800) {
+                               ms.WriteByte((byte)(0xC0 + (cp >> 6)));
+                               ms.WriteByte((byte)(0x80 + (cp & 63)));
+                       } else if (cp < 0x10000) {
+                               ms.WriteByte((byte)(0xE0 + (cp >> 12)));
+                               ms.WriteByte((byte)(0x80 + ((cp >> 6) & 63)));
+                               ms.WriteByte((byte)(0x80 + (cp & 63)));
+                       } else {
+                               ms.WriteByte((byte)(0xF0 + (cp >> 18)));
+                               ms.WriteByte((byte)(0x80 + ((cp >> 12) & 63)));
+                               ms.WriteByte((byte)(0x80 + ((cp >> 6) & 63)));
+                               ms.WriteByte((byte)(0x80 + (cp & 63)));
+                       }
+               }
+               return ms.ToArray();
+       }
+
+       static byte[] EncodeUTF16(string str)
+       {
+               byte[] buf = new byte[str.Length << 1];
+               int k = 0;
+               foreach (char c in str) {
+                       buf[k ++] = (byte)(c >> 8);
+                       buf[k ++] = (byte)c;
+               }
+               return buf;
+       }
+
+       static byte[] EncodeUTF32(string str)
+       {
+               int k = 0;
+               int n = str.Length;
+               MemoryStream ms = new MemoryStream();
+               while (k < n) {
+                       int cp = CodePoint(str, ref k);
+                       ms.WriteByte((byte)(cp >> 24));
+                       ms.WriteByte((byte)(cp >> 16));
+                       ms.WriteByte((byte)(cp >> 8));
+                       ms.WriteByte((byte)cp);
+               }
+               return ms.ToArray();
+       }
+
+       /*
+        * Create a time value of the specified type (UTCTime or
+        * GeneralizedTime).
+        */
+       public static AsnElt MakeTime(int type, DateTime dt)
+       {
+               dt = dt.ToUniversalTime();
+               string str;
+               switch (type) {
+               case UTCTime:
+                       int year = dt.Year;
+                       if (year < 1950 || year >= 2050) {
+                               throw new AsnException(String.Format(
+                                       "cannot encode year {0} as UTCTime",
+                                       year));
+                       }
+                       year = year % 100;
+                       str = String.Format(
+                               "{0:d2}{1:d2}{2:d2}{3:d2}{4:d2}{5:d2}Z",
+                               year, dt.Month, dt.Day,
+                               dt.Hour, dt.Minute, dt.Second);
+                       break;
+               case GeneralizedTime:
+                       str = String.Format(
+                               "{0:d4}{1:d2}{2:d2}{3:d2}{4:d2}{5:d2}",
+                               dt.Year, dt.Month, dt.Day,
+                               dt.Hour, dt.Minute, dt.Second);
+                       int millis = dt.Millisecond;
+                       if (millis != 0) {
+                               if (millis % 100 == 0) {
+                                       str = String.Format("{0}.{1:d1}",
+                                               str, millis / 100);
+                               } else if (millis % 10 == 0) {
+                                       str = String.Format("{0}.{1:d2}",
+                                               str, millis / 10);
+                               } else {
+                                       str = String.Format("{0}.{1:d3}",
+                                               str, millis);
+                               }
+                       }
+                       str = str + "Z";
+                       break;
+               default:
+                       throw new AsnException(
+                               "unsupported time type: " + type);
+               }
+               return MakeString(type, str);
+       }
+
+       /*
+        * Create a time value of the specified type (UTCTime or
+        * GeneralizedTime).
+        */
+       public static AsnElt MakeTime(int type, DateTimeOffset dto)
+       {
+               return MakeTime(type, dto.UtcDateTime);
+       }
+
+       /*
+        * Create a time value with an automatic type selection
+        * (UTCTime if year is in the 1950..2049 range, GeneralizedTime
+        * otherwise).
+        */
+       public static AsnElt MakeTimeAuto(DateTime dt)
+       {
+               dt = dt.ToUniversalTime();
+               return MakeTime((dt.Year >= 1950 && dt.Year <= 2049)
+                       ? UTCTime : GeneralizedTime, dt);
+       }
+
+       /*
+        * Create a time value with an automatic type selection
+        * (UTCTime if year is in the 1950..2049 range, GeneralizedTime
+        * otherwise).
+        */
+       public static AsnElt MakeTimeAuto(DateTimeOffset dto)
+       {
+               return MakeTimeAuto(dto.UtcDateTime);
+       }
+}
+
+}
diff --git a/Asn1/AsnException.cs b/Asn1/AsnException.cs
new file mode 100644 (file)
index 0000000..a2e6555
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.IO;
+
+namespace Asn1 {
+
+/*
+ * An AsnException is thrown whenever decoding of an object fails. It is
+ * made a subclass of IOException under the assumption that decoding
+ * failures mostly occur when processing incoming data from external
+ * sources.
+ */
+
+public class AsnException : IOException {
+
+       public AsnException(string message)
+               : base(message)
+       {
+       }
+
+       public AsnException(string message, Exception nested)
+               : base(message, nested)
+       {
+       }
+}
+
+}
diff --git a/Asn1/AsnIO.cs b/Asn1/AsnIO.cs
new file mode 100644 (file)
index 0000000..fb8cd3f
--- /dev/null
@@ -0,0 +1,402 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace Asn1 {
+
+/*
+ * Helper functions for located BER/DER-encoded object, and in particular
+ * handle PEM format.
+ */
+
+public static class AsnIO {
+
+       public static byte[] FindDER(byte[] buf)
+       {
+               return FindBER(buf, true);
+       }
+
+       public static byte[] FindBER(byte[] buf)
+       {
+               return FindBER(buf, false);
+       }
+
+       /*
+        * Find a BER/DER object in the provided buffer. If the data is
+        * not already in the right format, conversion to string then
+        * Base64 decoding is attempted; in the latter case, PEM headers
+        * are detected and skipped. In any case, the returned buffer
+        * must begin with a well-formed tag and length, corresponding to
+        * the object length.
+        *
+        * If 'strictDER' is true, then the function furthermore insists
+        * on the object to use a defined DER length.
+        *
+        * The returned buffer may be the source buffer itself, or a newly
+        * allocated buffer.
+        *
+        * On error, null is returned.
+        */
+       public static byte[] FindBER(byte[] buf, bool strictDER)
+       {
+               string pemType = null;
+               return FindBER(buf, strictDER, out pemType);
+       }
+
+       /*
+        * Find a BER/DER object in the provided buffer. If the data is
+        * not already in the right format, conversion to string then
+        * Base64 decoding is attempted; in the latter case, PEM headers
+        * are detected and skipped. In any case, the returned buffer
+        * must begin with a well-formed tag and length, corresponding to
+        * the object length.
+        *
+        * If 'strictDER' is true, then the function furthermore insists
+        * on the object to use a defined DER length.
+        *
+        * If the source was detected to use PEM, then the object type
+        * indicated by the PEM header is written in 'pemType'; otherwise,
+        * that variable is set to null.
+        *
+        * The returned buffer may be the source buffer itself, or a newly
+        * allocated buffer.
+        *
+        * On error, null is returned.
+        */
+       public static byte[] FindBER(byte[] buf,
+               bool strictDER, out string pemType)
+       {
+               pemType = null;
+
+               /*
+                * If it is already (from the outside) a BER object,
+                * return it.
+                */
+               if (LooksLikeBER(buf, strictDER)) {
+                       return buf;
+               }
+
+               string str = BinToString(buf);
+               if (str == null) {
+                       return null;
+               }
+
+               /*
+                * Try to detect a PEM header and footer; if we find both
+                * then we remove both, keeping only the characters that
+                * occur in between.
+                */
+               int p = str.IndexOf("-----BEGIN ");
+               int q = str.IndexOf("-----END ");
+               if (p >= 0 && q >= 0) {
+                       p += 11;
+                       int r = str.IndexOf((char)10, p) + 1;
+                       int px = str.IndexOf('-', p);
+                       if (px > 0 && px < r && r > 0 && r <= q) {
+                               pemType = string.Copy(str.Substring(p, px - p));
+                               str = str.Substring(r, q - r);
+                       }
+               }
+
+               /*
+                * Convert from Base64.
+                */
+               try {
+                       buf = Convert.FromBase64String(str);
+                       if (LooksLikeBER(buf, strictDER)) {
+                               return buf;
+                       }
+               } catch {
+                       // ignored: not Base64
+               }
+
+               /*
+                * Decoding failed.
+                */
+               return null;
+       }
+
+       /*
+        * Decode multiple PEM objects from a file. Each object is
+        * returned with its name.
+        */
+       public static PEMObject[] DecodePEM(byte[] buf)
+       {
+               string str = BinToString(buf);
+               if (str == null) {
+                       return new PEMObject[0];
+               }
+
+               List<PEMObject> fpo = new List<PEMObject>();
+               TextReader tr = new StringReader(str);
+               StringBuilder sb = new StringBuilder();
+               string currentType = null;
+               for (;;) {
+                       string line = tr.ReadLine();
+                       if (line == null) {
+                               break;
+                       }
+                       if (currentType == null) {
+                               if (line.StartsWith("-----BEGIN ")) {
+                                       line = line.Substring(11);
+                                       int n = line.IndexOf("-----");
+                                       if (n >= 0) {
+                                               line = line.Substring(0, n);
+                                       }
+                                       currentType = string.Copy(line);
+                                       sb = new StringBuilder();
+                               }
+                               continue;
+                       }
+                       if (line.StartsWith("-----END ")) {
+                               string s = sb.ToString();
+                               try {
+                                       byte[] data =
+                                               Convert.FromBase64String(s);
+                                       fpo.Add(new PEMObject(
+                                               currentType, data));
+                               } catch {
+                                       /*
+                                        * Base64 decoding failed... we skip
+                                        * the object.
+                                        */
+                               }
+                               currentType = null;
+                               sb.Clear();
+                               continue;
+                       }
+                       sb.Append(line);
+                       sb.Append("\n");
+               }
+               return fpo.ToArray();
+       }
+
+       /* =============================================================== */
+
+       /*
+        * Decode a tag; returned value is true on success, false otherwise.
+        * On success, 'off' is updated to point to the first byte after
+        * the tag.
+        */
+       static bool DecodeTag(byte[] buf, int lim, ref int off)
+       {
+               int p = off;
+               if (p >= lim) {
+                       return false;
+               }
+               int v = buf[p ++];
+               if ((v & 0x1F) == 0x1F) {
+                       do {
+                               if (p >= lim) {
+                                       return false;
+                               }
+                               v = buf[p ++];
+                       } while ((v & 0x80) != 0);
+               }
+               off = p;
+               return true;
+       }
+
+       /*
+        * Decode a BER length. Returned value is:
+        *   -2   no decodable length
+        *   -1   indefinite length
+        *    0+  definite length
+        * If a definite or indefinite length could be decoded, then 'off'
+        * is updated to point to the first byte after the length.
+        */
+       static int DecodeLength(byte[] buf, int lim, ref int off)
+       {
+               int p = off;
+               if (p >= lim) {
+                       return -2;
+               }
+               int v = buf[p ++];
+               if (v < 0x80) {
+                       off = p;
+                       return v;
+               } else if (v == 0x80) {
+                       off = p;
+                       return -1;
+               }
+               v &= 0x7F;
+               if ((lim - p) < v) {
+                       return -2;
+               }
+               int acc = 0;
+               while (v -- > 0) {
+                       if (acc > 0x7FFFFF) {
+                               return -2;
+                       }
+                       acc = (acc << 8) + buf[p ++];
+               }
+               off = p;
+               return acc;
+       }
+
+       /*
+        * Get the length, in bytes, of the object in the provided
+        * buffer. The object begins at offset 'off' but does not extend
+        * farther than offset 'lim'. If no such BER object can be
+        * decoded, then -1 is returned. The returned length includes
+        * that of the tag and length fields.
+        */
+       static int BERLength(byte[] buf, int lim, int off)
+       {
+               int orig = off;
+               if (!DecodeTag(buf, lim, ref off)) {
+                       return -1;
+               }
+               int len = DecodeLength(buf, lim, ref off);
+               if (len >= 0) {
+                       if (len > (lim - off)) {
+                               return -1;
+                       }
+                       return off + len - orig;
+               } else if (len < -1) {
+                       return -1;
+               }
+
+               /*
+                * Indefinite length: we must do some recursive exploration.
+                * End of structure is marked by a "null tag": object has
+                * total length 2 and its tag byte is 0.
+                */
+               for (;;) {
+                       int slen = BERLength(buf, lim, off);
+                       if (slen < 0) {
+                               return -1;
+                       }
+                       off += slen;
+                       if (slen == 2 && buf[off] == 0) {
+                               return off - orig;
+                       }
+               }
+       }
+
+       static bool LooksLikeBER(byte[] buf, bool strictDER)
+       {
+               return LooksLikeBER(buf, 0, buf.Length, strictDER);
+       }
+
+       static bool LooksLikeBER(byte[] buf, int off, int len, bool strictDER)
+       {
+               int lim = off + len;
+               int objLen = BERLength(buf, lim, off);
+               if (objLen != len) {
+                       return false;
+               }
+               if (strictDER) {
+                       DecodeTag(buf, lim, ref off);
+                       return DecodeLength(buf, lim, ref off) >= 0;
+               } else {
+                       return true;
+               }
+       }
+
+       static string ConvertMono(byte[] buf, int off)
+       {
+               int len = buf.Length - off;
+               char[] tc = new char[len];
+               for (int i = 0; i < len; i ++) {
+                       int v = buf[off + i];
+                       if (v < 1 || v > 126) {
+                               v = '?';
+                       }
+                       tc[i] = (char)v;
+               }
+               return new string(tc);
+       }
+
+       static string ConvertBi(byte[] buf, int off, bool be)
+       {
+               int len = buf.Length - off;
+               if ((len & 1) != 0) {
+                       return null;
+               }
+               len >>= 1;
+               char[] tc = new char[len];
+               for (int i = 0; i < len; i ++) {
+                       int b0 = buf[off + (i << 1) + 0];
+                       int b1 = buf[off + (i << 1) + 1];
+                       int v = be ? ((b0 << 8) + b1) : (b0 + (b1 << 8));
+                       if (v < 1 || v > 126) {
+                               v = '?';
+                       }
+                       tc[i] = (char)v;
+               }
+               return new string(tc);
+       }
+
+       /*
+        * Convert a blob to a string. This supports UTF-16 (with and
+        * without a BOM), UTF-8 (with and without a BOM), and
+        * ASCII-compatible encodings. Non-ASCII characters get
+        * replaced with '?'. This function is meant to be used
+        * with heuristic PEM decoders.
+        *
+        * If conversion is not possible, then null is returned.
+        */
+       static string BinToString(byte[] buf)
+       {
+               if (buf.Length < 3) {
+                       return null;
+               }
+               string str = null;
+               if ((buf.Length & 1) == 0) {
+                       if (buf[0] == 0xFE && buf[1] == 0xFF) {
+                               // Starts with big-endian UTF-16 BOM
+                               str = ConvertBi(buf, 2, true);
+                       } else if (buf[0] == 0xFF && buf[1] == 0xFE) {
+                               // Starts with little-endian UTF-16 BOM
+                               str = ConvertBi(buf, 2, false);
+                       } else if (buf[0] == 0) {
+                               // First byte is 0 -> big-endian UTF-16
+                               str = ConvertBi(buf, 0, true);
+                       } else if (buf[1] == 0) {
+                               // Second byte is 0 -> little-endian UTF-16
+                               str = ConvertBi(buf, 0, false);
+                       }
+               }
+               if (str == null) {
+                       if (buf[0] == 0xEF
+                               && buf[1] == 0xBB
+                               && buf[2] == 0xBF)
+                       {
+                               // Starts with UTF-8 BOM
+                               str = ConvertMono(buf, 3);
+                       } else {
+                               // Assumed ASCII-compatible mono-byte encoding
+                               str = ConvertMono(buf, 0);
+                       }
+               }
+               return str;
+       }
+}
+
+}
diff --git a/Asn1/AsnOID.cs b/Asn1/AsnOID.cs
new file mode 100644 (file)
index 0000000..e794356
--- /dev/null
@@ -0,0 +1,335 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Asn1 {
+
+/*
+ * This class contains some helper methods to convert known OID to
+ * symbolic names and back.
+ */
+
+public class AsnOID {
+
+       static Dictionary<string, string> OIDToName =
+               new Dictionary<string, string>();
+       static Dictionary<string, string> NameToOID =
+               new Dictionary<string, string>();
+
+       static AsnOID()
+       {
+               /*
+                * From RFC 5280, PKIX1Explicit88 module.
+                */
+               Reg("1.3.6.1.5.5.7", "id-pkix");
+               Reg("1.3.6.1.5.5.7.1", "id-pe");
+               Reg("1.3.6.1.5.5.7.2", "id-qt");
+               Reg("1.3.6.1.5.5.7.3", "id-kp");
+               Reg("1.3.6.1.5.5.7.48", "id-ad");
+               Reg("1.3.6.1.5.5.7.2.1", "id-qt-cps");
+               Reg("1.3.6.1.5.5.7.2.2", "id-qt-unotice");
+               Reg("1.3.6.1.5.5.7.48.1", "id-ad-ocsp");
+               Reg("1.3.6.1.5.5.7.48.2", "id-ad-caIssuers");
+               Reg("1.3.6.1.5.5.7.48.3", "id-ad-timeStamping");
+               Reg("1.3.6.1.5.5.7.48.5", "id-ad-caRepository");
+
+               Reg("2.5.4", "id-at");
+               Reg("2.5.4.41", "id-at-name");
+               Reg("2.5.4.4", "id-at-surname");
+               Reg("2.5.4.42", "id-at-givenName");
+               Reg("2.5.4.43", "id-at-initials");
+               Reg("2.5.4.44", "id-at-generationQualifier");
+               Reg("2.5.4.3", "id-at-commonName");
+               Reg("2.5.4.7", "id-at-localityName");
+               Reg("2.5.4.8", "id-at-stateOrProvinceName");
+               Reg("2.5.4.10", "id-at-organizationName");
+               Reg("2.5.4.11", "id-at-organizationalUnitName");
+               Reg("2.5.4.12", "id-at-title");
+               Reg("2.5.4.46", "id-at-dnQualifier");
+               Reg("2.5.4.6", "id-at-countryName");
+               Reg("2.5.4.5", "id-at-serialNumber");
+               Reg("2.5.4.65", "id-at-pseudonym");
+               Reg("0.9.2342.19200300.100.1.25", "id-domainComponent");
+
+               Reg("1.2.840.113549.1.9", "pkcs-9");
+               Reg("1.2.840.113549.1.9.1", "id-emailAddress");
+
+               /*
+                * From RFC 5280, PKIX1Implicit88 module.
+                */
+               Reg("2.5.29", "id-ce");
+               Reg("2.5.29.35", "id-ce-authorityKeyIdentifier");
+               Reg("2.5.29.14", "id-ce-subjectKeyIdentifier");
+               Reg("2.5.29.15", "id-ce-keyUsage");
+               Reg("2.5.29.16", "id-ce-privateKeyUsagePeriod");
+               Reg("2.5.29.32", "id-ce-certificatePolicies");
+               Reg("2.5.29.33", "id-ce-policyMappings");
+               Reg("2.5.29.17", "id-ce-subjectAltName");
+               Reg("2.5.29.18", "id-ce-issuerAltName");
+               Reg("2.5.29.9", "id-ce-subjectDirectoryAttributes");
+               Reg("2.5.29.19", "id-ce-basicConstraints");
+               Reg("2.5.29.30", "id-ce-nameConstraints");
+               Reg("2.5.29.36", "id-ce-policyConstraints");
+               Reg("2.5.29.31", "id-ce-cRLDistributionPoints");
+               Reg("2.5.29.37", "id-ce-extKeyUsage");
+
+               Reg("2.5.29.37.0", "anyExtendedKeyUsage");
+               Reg("1.3.6.1.5.5.7.3.1", "id-kp-serverAuth");
+               Reg("1.3.6.1.5.5.7.3.2", "id-kp-clientAuth");
+               Reg("1.3.6.1.5.5.7.3.3", "id-kp-codeSigning");
+               Reg("1.3.6.1.5.5.7.3.4", "id-kp-emailProtection");
+               Reg("1.3.6.1.5.5.7.3.8", "id-kp-timeStamping");
+               Reg("1.3.6.1.5.5.7.3.9", "id-kp-OCSPSigning");
+
+               Reg("2.5.29.54", "id-ce-inhibitAnyPolicy");
+               Reg("2.5.29.46", "id-ce-freshestCRL");
+               Reg("1.3.6.1.5.5.7.1.1", "id-pe-authorityInfoAccess");
+               Reg("1.3.6.1.5.5.7.1.11", "id-pe-subjectInfoAccess");
+               Reg("2.5.29.20", "id-ce-cRLNumber");
+               Reg("2.5.29.28", "id-ce-issuingDistributionPoint");
+               Reg("2.5.29.27", "id-ce-deltaCRLIndicator");
+               Reg("2.5.29.21", "id-ce-cRLReasons");
+               Reg("2.5.29.29", "id-ce-certificateIssuer");
+               Reg("2.5.29.23", "id-ce-holdInstructionCode");
+               Reg("2.2.840.10040.2", "WRONG-holdInstruction");
+               Reg("2.2.840.10040.2.1", "WRONG-id-holdinstruction-none");
+               Reg("2.2.840.10040.2.2", "WRONG-id-holdinstruction-callissuer");
+               Reg("2.2.840.10040.2.3", "WRONG-id-holdinstruction-reject");
+               Reg("2.5.29.24", "id-ce-invalidityDate");
+
+               /*
+                * These are the "right" OID. RFC 5280 mistakenly defines
+                * the first OID element as "2".
+                */
+               Reg("1.2.840.10040.2", "holdInstruction");
+               Reg("1.2.840.10040.2.1", "id-holdinstruction-none");
+               Reg("1.2.840.10040.2.2", "id-holdinstruction-callissuer");
+               Reg("1.2.840.10040.2.3", "id-holdinstruction-reject");
+
+               /*
+                * From PKCS#1.
+                */
+               Reg("1.2.840.113549.1.1", "pkcs-1");
+               Reg("1.2.840.113549.1.1.1", "rsaEncryption");
+               Reg("1.2.840.113549.1.1.7", "id-RSAES-OAEP");
+               Reg("1.2.840.113549.1.1.9", "id-pSpecified");
+               Reg("1.2.840.113549.1.1.10", "id-RSASSA-PSS");
+               Reg("1.2.840.113549.1.1.2", "md2WithRSAEncryption");
+               Reg("1.2.840.113549.1.1.4", "md5WithRSAEncryption");
+               Reg("1.2.840.113549.1.1.5", "sha1WithRSAEncryption");
+               Reg("1.2.840.113549.1.1.11", "sha256WithRSAEncryption");
+               Reg("1.2.840.113549.1.1.12", "sha384WithRSAEncryption");
+               Reg("1.2.840.113549.1.1.13", "sha512WithRSAEncryption");
+               Reg("1.3.14.3.2.26", "id-sha1");
+               Reg("1.2.840.113549.2.2", "id-md2");
+               Reg("1.2.840.113549.2.5", "id-md5");
+               Reg("1.2.840.113549.1.1.8", "id-mgf1");
+
+               /*
+                * From NIST: http://csrc.nist.gov/groups/ST/crypto_apps_infra/csor/algorithms.html
+                */
+               Reg("2.16.840.1.101.3", "csor");
+               Reg("2.16.840.1.101.3.4", "nistAlgorithms");
+               Reg("2.16.840.1.101.3.4.0", "csorModules");
+               Reg("2.16.840.1.101.3.4.0.1", "aesModule1");
+
+               Reg("2.16.840.1.101.3.4.1", "aes");
+               Reg("2.16.840.1.101.3.4.1.1", "id-aes128-ECB");
+               Reg("2.16.840.1.101.3.4.1.2", "id-aes128-CBC");
+               Reg("2.16.840.1.101.3.4.1.3", "id-aes128-OFB");
+               Reg("2.16.840.1.101.3.4.1.4", "id-aes128-CFB");
+               Reg("2.16.840.1.101.3.4.1.5", "id-aes128-wrap");
+               Reg("2.16.840.1.101.3.4.1.6", "id-aes128-GCM");
+               Reg("2.16.840.1.101.3.4.1.7", "id-aes128-CCM");
+               Reg("2.16.840.1.101.3.4.1.8", "id-aes128-wrap-pad");
+               Reg("2.16.840.1.101.3.4.1.21", "id-aes192-ECB");
+               Reg("2.16.840.1.101.3.4.1.22", "id-aes192-CBC");
+               Reg("2.16.840.1.101.3.4.1.23", "id-aes192-OFB");
+               Reg("2.16.840.1.101.3.4.1.24", "id-aes192-CFB");
+               Reg("2.16.840.1.101.3.4.1.25", "id-aes192-wrap");
+               Reg("2.16.840.1.101.3.4.1.26", "id-aes192-GCM");
+               Reg("2.16.840.1.101.3.4.1.27", "id-aes192-CCM");
+               Reg("2.16.840.1.101.3.4.1.28", "id-aes192-wrap-pad");
+               Reg("2.16.840.1.101.3.4.1.41", "id-aes256-ECB");
+               Reg("2.16.840.1.101.3.4.1.42", "id-aes256-CBC");
+               Reg("2.16.840.1.101.3.4.1.43", "id-aes256-OFB");
+               Reg("2.16.840.1.101.3.4.1.44", "id-aes256-CFB");
+               Reg("2.16.840.1.101.3.4.1.45", "id-aes256-wrap");
+               Reg("2.16.840.1.101.3.4.1.46", "id-aes256-GCM");
+               Reg("2.16.840.1.101.3.4.1.47", "id-aes256-CCM");
+               Reg("2.16.840.1.101.3.4.1.48", "id-aes256-wrap-pad");
+
+               Reg("2.16.840.1.101.3.4.2", "hashAlgs");
+               Reg("2.16.840.1.101.3.4.2.1", "id-sha256");
+               Reg("2.16.840.1.101.3.4.2.2", "id-sha384");
+               Reg("2.16.840.1.101.3.4.2.3", "id-sha512");
+               Reg("2.16.840.1.101.3.4.2.4", "id-sha224");
+               Reg("2.16.840.1.101.3.4.2.5", "id-sha512-224");
+               Reg("2.16.840.1.101.3.4.2.6", "id-sha512-256");
+
+               Reg("2.16.840.1.101.3.4.3", "sigAlgs");
+               Reg("2.16.840.1.101.3.4.3.1", "id-dsa-with-sha224");
+               Reg("2.16.840.1.101.3.4.3.2", "id-dsa-with-sha256");
+
+               Reg("1.2.840.113549", "rsadsi");
+               Reg("1.2.840.113549.2", "digestAlgorithm");
+               Reg("1.2.840.113549.2.7", "id-hmacWithSHA1");
+               Reg("1.2.840.113549.2.8", "id-hmacWithSHA224");
+               Reg("1.2.840.113549.2.9", "id-hmacWithSHA256");
+               Reg("1.2.840.113549.2.10", "id-hmacWithSHA384");
+               Reg("1.2.840.113549.2.11", "id-hmacWithSHA512");
+
+               /*
+                * From X9.57: http://oid-info.com/get/1.2.840.10040.4
+                */
+               Reg("1.2.840.10040.4", "x9algorithm");
+               Reg("1.2.840.10040.4", "x9cm");
+               Reg("1.2.840.10040.4.1", "dsa");
+               Reg("1.2.840.10040.4.3", "dsa-with-sha1");
+
+               /*
+                * From SEC: http://oid-info.com/get/1.3.14.3.2
+                */
+               Reg("1.3.14.3.2.2", "md4WithRSA");
+               Reg("1.3.14.3.2.3", "md5WithRSA");
+               Reg("1.3.14.3.2.4", "md4WithRSAEncryption");
+               Reg("1.3.14.3.2.12", "dsaSEC");
+               Reg("1.3.14.3.2.13", "dsaWithSHASEC");
+               Reg("1.3.14.3.2.27", "dsaWithSHA1SEC");
+
+               /*
+                * From Microsoft: http://oid-info.com/get/1.3.6.1.4.1.311.20.2
+                */
+               Reg("1.3.6.1.4.1.311.20.2", "ms-certType");
+               Reg("1.3.6.1.4.1.311.20.2.2", "ms-smartcardLogon");
+               Reg("1.3.6.1.4.1.311.20.2.3", "ms-UserPrincipalName");
+               Reg("1.3.6.1.4.1.311.20.2.3", "ms-UPN");
+       }
+
+       static void Reg(string oid, string name)
+       {
+               if (!OIDToName.ContainsKey(oid)) {
+                       OIDToName.Add(oid, name);
+               }
+               string nn = Normalize(name);
+               if (NameToOID.ContainsKey(nn)) {
+                       throw new Exception("OID name collision: " + nn);
+               }
+               NameToOID.Add(nn, oid);
+
+               /*
+                * Many names start with 'id-??-' and we want to support
+                * the short names (without that prefix) as aliases. But
+                * we must take care of some collisions on short names.
+                */
+               if (name.StartsWith("id-")
+                       && name.Length >= 7 && name[5] == '-')
+               {
+                       if (name.StartsWith("id-ad-")) {
+                               Reg(oid, name.Substring(6) + "-IA");
+                       } else if (name.StartsWith("id-kp-")) {
+                               Reg(oid, name.Substring(6) + "-EKU");
+                       } else {
+                               Reg(oid, name.Substring(6));
+                       }
+               }
+       }
+
+       static string Normalize(string name)
+       {
+               StringBuilder sb = new StringBuilder();
+               foreach (char c in name) {
+                       int d = (int)c;
+                       if (d <= 32 || d == '-') {
+                               continue;
+                       }
+                       if (d >= 'A' && d <= 'Z') {
+                               d += 'a' - 'A';
+                       }
+                       sb.Append((char)c);
+               }
+               return sb.ToString();
+       }
+
+       /*
+        * Convert an OID in numeric form to its symbolic name, if known.
+        * Otherwise, the provided string is returned as is.
+        */
+       public static string ToName(string oid)
+       {
+               return OIDToName.ContainsKey(oid) ? OIDToName[oid] : oid;
+       }
+
+       /*
+        * Convert a symbolic OID name to its numeric representation. If
+        * the source string is already numeric OID, then it is returned
+        * as is. Otherwise, the symbolic name is looked up; this lookup
+        * ignores case as well as spaces and dash characters. If the name
+        * is not recognized, an AsnException is thrown.
+        */
+       public static string ToOID(string name)
+       {
+               if (IsNumericOID(name)) {
+                       return name;
+               }
+               string nn = Normalize(name);
+               if (!NameToOID.ContainsKey(nn)) {
+                       throw new AsnException(
+                               "unrecognized OID name: " + name);
+               }
+               return NameToOID[nn];
+       }
+
+       /*
+        * Test whether a given string "looks like" an OID in numeric
+        * representation. Criteria applied by this function:
+        *  - only decimal ASCII digits and dots
+        *  - does not start or end with a dot
+        *  - at least one dot
+        *  - no two consecutive dots
+        */
+       public static bool IsNumericOID(string oid)
+       {
+               foreach (char c in oid) {
+                       if (!(c >= '0' && c <= '9') && c != '.') {
+                               return false;
+                       }
+               }
+               if (oid.StartsWith(".") || oid.EndsWith(".")) {
+                       return false;
+               }
+               if (oid.IndexOf("..") >= 0) {
+                       return false;
+               }
+               if (oid.IndexOf('.') < 0) {
+                       return false;
+               }
+               return true;
+       }
+}
+
+}
diff --git a/Asn1/PEMObject.cs b/Asn1/PEMObject.cs
new file mode 100644 (file)
index 0000000..9be6c1a
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Asn1 {
+
+/*
+ * A PEMObject is a structure that corresponds to a binary object that
+ * was found PEM-encoded in some source stream. The object contents and
+ * PEM type are provided.
+ */
+
+public struct PEMObject {
+
+       public string type;
+       public byte[] data;
+
+       public PEMObject(string type, byte[] data)
+       {
+               this.type = type;
+               this.data = data;
+       }
+}
+
+}
diff --git a/Crypto/AES.cs b/Crypto/AES.cs
new file mode 100644 (file)
index 0000000..1ffd454
--- /dev/null
@@ -0,0 +1,433 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Classic AES implementation, with tables (8->32 lookup tables, four
+ * tables for encryption, four other tables for decryption). This is
+ * relatively efficient, but not constant-time.
+ */
+
+public sealed class AES : BlockCipherCore {
+
+       uint[] skey;
+       uint[] iskey;
+       int rounds;
+
+       /*
+        * Initialize a new instance.
+        */
+       public AES()
+       {
+               skey = new uint[4 * 15];
+               iskey = new uint[skey.Length];
+       }
+
+       /* see IBlockCipher */
+       public override IBlockCipher Dup()
+       {
+               AES a = new AES();
+               Array.Copy(skey, 0, a.skey, 0, skey.Length);
+               Array.Copy(iskey, 0, a.iskey, 0, iskey.Length);
+               a.rounds = rounds;
+               return a;
+       }
+
+       /*
+        * Get the block size in bytes (always 16).
+        */
+       public override int BlockSize {
+               get {
+                       return 16;
+               }
+       }
+
+       /*
+        * Set the key (16, 24 or 32 bytes).
+        */
+       public override void SetKey(byte[] key, int off, int len)
+       {
+               switch (len) {
+               case 16:
+                       rounds = 10;
+                       break;
+               case 24:
+                       rounds = 12;
+                       break;
+               case 32:
+                       rounds = 14;
+                       break;
+               default:
+                       throw new ArgumentException(
+                               "bad AES key length: " + len);
+               }
+               int nk = len >> 2;
+               int nkf = (rounds + 1) << 2;
+               for (int i = 0; i < nk; i ++) {
+                       skey[i] = Dec32be(key, off + (i << 2));
+               }
+               for (int i = nk, j = 0, k = 0; i < nkf; i ++) {
+                       uint tmp = skey[i - 1];
+                       if (j == 0) {
+                               tmp = (tmp << 8) | (tmp >> 24);
+                               tmp = SubWord(tmp) ^ Rcon[k];
+                       } else if (nk > 6 && j == 4) {
+                               tmp = SubWord(tmp);
+                       }
+                       skey[i] = skey[i - nk] ^ tmp;
+                       if (++ j == nk) {
+                               j = 0;
+                               k ++;
+                       }
+               }
+
+               /*
+                * Subkeys for decryption (with InvMixColumns() already
+                * applied for the inner rounds).
+                */
+               Array.Copy(skey, 0, iskey, 0, 4);
+               for (int i = 4; i < (rounds << 2); i ++) {
+                       uint p = skey[i];
+                       uint p0 = p >> 24;
+                       uint p1 = (p >> 16) & 0xFF;
+                       uint p2 = (p >> 8) & 0xFF;
+                       uint p3 = p & 0xFF;
+                       uint q0 = mule(p0) ^ mulb(p1) ^ muld(p2) ^ mul9(p3);
+                       uint q1 = mul9(p0) ^ mule(p1) ^ mulb(p2) ^ muld(p3);
+                       uint q2 = muld(p0) ^ mul9(p1) ^ mule(p2) ^ mulb(p3);
+                       uint q3 = mulb(p0) ^ muld(p1) ^ mul9(p2) ^ mule(p3);
+                       iskey[i] = (q0 << 24) | (q1 << 16) | (q2 << 8) | q3;
+               }
+               Array.Copy(skey, rounds << 2, iskey, rounds << 2, 4);
+       }
+
+       /* see IBlockCipher */
+       public override void BlockEncrypt(byte[] buf, int off)
+       {
+               uint s0 = Dec32be(buf, off);
+               uint s1 = Dec32be(buf, off + 4);
+               uint s2 = Dec32be(buf, off + 8);
+               uint s3 = Dec32be(buf, off + 12);
+               s0 ^= skey[0];
+               s1 ^= skey[1];
+               s2 ^= skey[2];
+               s3 ^= skey[3];
+               for (int i = 1; i < rounds; i ++) {
+                       uint v0 = Ssm0[s0 >> 24]
+                               ^ Ssm1[(s1 >> 16) & 0xFF]
+                               ^ Ssm2[(s2 >> 8) & 0xFF]
+                               ^ Ssm3[s3 & 0xFF];
+                       uint v1 = Ssm0[s1 >> 24]
+                               ^ Ssm1[(s2 >> 16) & 0xFF]
+                               ^ Ssm2[(s3 >> 8) & 0xFF]
+                               ^ Ssm3[s0 & 0xFF];
+                       uint v2 = Ssm0[s2 >> 24]
+                               ^ Ssm1[(s3 >> 16) & 0xFF]
+                               ^ Ssm2[(s0 >> 8) & 0xFF]
+                               ^ Ssm3[s1 & 0xFF];
+                       uint v3 = Ssm0[s3 >> 24]
+                               ^ Ssm1[(s0 >> 16) & 0xFF]
+                               ^ Ssm2[(s1 >> 8) & 0xFF]
+                               ^ Ssm3[s2 & 0xFF];
+                       s0 = v0;
+                       s1 = v1;
+                       s2 = v2;
+                       s3 = v3;
+                       s0 ^= skey[i << 2];
+                       s1 ^= skey[(i << 2) + 1];
+                       s2 ^= skey[(i << 2) + 2];
+                       s3 ^= skey[(i << 2) + 3];
+               }
+               uint t0 = (S[s0 >> 24] << 24)
+                       | (S[(s1 >> 16) & 0xFF] << 16)
+                       | (S[(s2 >> 8) & 0xFF] << 8)
+                       | S[s3 & 0xFF];
+               uint t1 = (S[s1 >> 24] << 24)
+                       | (S[(s2 >> 16) & 0xFF] << 16)
+                       | (S[(s3 >> 8) & 0xFF] << 8)
+                       | S[s0 & 0xFF];
+               uint t2 = (S[s2 >> 24] << 24)
+                       | (S[(s3 >> 16) & 0xFF] << 16)
+                       | (S[(s0 >> 8) & 0xFF] << 8)
+                       | S[s1 & 0xFF];
+               uint t3 = (S[s3 >> 24] << 24)
+                       | (S[(s0 >> 16) & 0xFF] << 16)
+                       | (S[(s1 >> 8) & 0xFF] << 8)
+                       | S[s2 & 0xFF];
+               s0 = t0 ^ skey[rounds << 2];
+               s1 = t1 ^ skey[(rounds << 2) + 1];
+               s2 = t2 ^ skey[(rounds << 2) + 2];
+               s3 = t3 ^ skey[(rounds << 2) + 3];
+               Enc32be(s0, buf, off);
+               Enc32be(s1, buf, off + 4);
+               Enc32be(s2, buf, off + 8);
+               Enc32be(s3, buf, off + 12);
+       }
+
+       /* see IBlockCipher */
+       public override void BlockDecrypt(byte[] buf, int off)
+       {
+               uint s0 = Dec32be(buf, off);
+               uint s1 = Dec32be(buf, off + 4);
+               uint s2 = Dec32be(buf, off + 8);
+               uint s3 = Dec32be(buf, off + 12);
+               s0 ^= iskey[rounds << 2];
+               s1 ^= iskey[(rounds << 2) + 1];
+               s2 ^= iskey[(rounds << 2) + 2];
+               s3 ^= iskey[(rounds << 2) + 3];
+               for (int i = rounds - 1; i > 0; i --) {
+                       uint v0 = iSsm0[s0 >> 24]
+                               ^ iSsm1[(s3 >> 16) & 0xFF]
+                               ^ iSsm2[(s2 >> 8) & 0xFF]
+                               ^ iSsm3[s1 & 0xFF];
+                       uint v1 = iSsm0[s1 >> 24]
+                               ^ iSsm1[(s0 >> 16) & 0xFF]
+                               ^ iSsm2[(s3 >> 8) & 0xFF]
+                               ^ iSsm3[s2 & 0xFF];
+                       uint v2 = iSsm0[s2 >> 24]
+                               ^ iSsm1[(s1 >> 16) & 0xFF]
+                               ^ iSsm2[(s0 >> 8) & 0xFF]
+                               ^ iSsm3[s3 & 0xFF];
+                       uint v3 = iSsm0[s3 >> 24]
+                               ^ iSsm1[(s2 >> 16) & 0xFF]
+                               ^ iSsm2[(s1 >> 8) & 0xFF]
+                               ^ iSsm3[s0 & 0xFF];
+                       s0 = v0;
+                       s1 = v1;
+                       s2 = v2;
+                       s3 = v3;
+                       s0 ^= iskey[i << 2];
+                       s1 ^= iskey[(i << 2) + 1];
+                       s2 ^= iskey[(i << 2) + 2];
+                       s3 ^= iskey[(i << 2) + 3];
+               }
+               uint t0 = (iS[s0 >> 24] << 24)
+                       | (iS[(s3 >> 16) & 0xFF] << 16)
+                       | (iS[(s2 >> 8) & 0xFF] << 8)
+                       | iS[s1 & 0xFF];
+               uint t1 = (iS[s1 >> 24] << 24)
+                       | (iS[(s0 >> 16) & 0xFF] << 16)
+                       | (iS[(s3 >> 8) & 0xFF] << 8)
+                       | iS[s2 & 0xFF];
+               uint t2 = (iS[s2 >> 24] << 24)
+                       | (iS[(s1 >> 16) & 0xFF] << 16)
+                       | (iS[(s0 >> 8) & 0xFF] << 8)
+                       | iS[s3 & 0xFF];
+               uint t3 = (iS[s3 >> 24] << 24)
+                       | (iS[(s2 >> 16) & 0xFF] << 16)
+                       | (iS[(s1 >> 8) & 0xFF] << 8)
+                       | iS[s0 & 0xFF];
+               s0 = t0 ^ iskey[0];
+               s1 = t1 ^ iskey[1];
+               s2 = t2 ^ iskey[2];
+               s3 = t3 ^ iskey[3];
+               Enc32be(s0, buf, off);
+               Enc32be(s1, buf, off + 4);
+               Enc32be(s2, buf, off + 8);
+               Enc32be(s3, buf, off + 12);
+       }
+
+       static uint Dec32be(byte[] buf, int off)
+       {
+               return ((uint)buf[off] << 24)
+                       | ((uint)buf[off + 1] << 16)
+                       | ((uint)buf[off + 2] << 8)
+                       | (uint)buf[off + 3];
+       }
+
+       static void Enc32be(uint x, byte[] buf, int off)
+       {
+               buf[off] = (byte)(x >> 24);
+               buf[off + 1] = (byte)(x >> 16);
+               buf[off + 2] = (byte)(x >> 8);
+               buf[off + 3] = (byte)x;
+       }
+
+       static uint mul2(uint x)
+       {
+               x <<= 1;
+               return x ^ ((uint)(-(int)(x >> 8)) & 0x11B);
+       }
+
+       static uint mul3(uint x)
+       {
+               return x ^ mul2(x);
+       }
+
+       static uint mul9(uint x)
+       {
+               return x ^ mul2(mul2(mul2(x)));
+       }
+
+       static uint mulb(uint x)
+       {
+               uint x2 = mul2(x);
+               return x ^ x2 ^ mul2(mul2(x2));
+       }
+
+       static uint muld(uint x)
+       {
+               uint x4 = mul2(mul2(x));
+               return x ^ x4 ^ mul2(x4);
+       }
+
+       static uint mule(uint x)
+       {
+               uint x2 = mul2(x);
+               uint x4 = mul2(x2);
+               return x2 ^ x4 ^ mul2(x4);
+       }
+
+       static uint aff(uint x)
+       {
+               x |= x << 8;
+               x ^= (x >> 4) ^ (x >> 5) ^ (x >> 6) ^ (x >> 7) ^ 0x63;
+               return x & 0xFF;
+       }
+
+       static uint[] Rcon;
+       static uint[] S;
+       static uint[] Ssm0, Ssm1, Ssm2, Ssm3;
+       static uint[] iS;
+       static uint[] iSsm0, iSsm1, iSsm2, iSsm3;
+
+       static uint SubWord(uint x)
+       {
+               return (S[x >> 24] << 24)
+                       | (S[(x >> 16) & 0xFF] << 16)
+                       | (S[(x >> 8) & 0xFF] << 8)
+                       | S[x & 0xFF];
+       }
+
+       static AES()
+       {
+               /*
+                * The Rcon[] constants are used in the key schedule.
+                */
+               Rcon = new uint[10];
+               uint x = 1;
+               for (int i = 0; i < Rcon.Length; i ++) {
+                       Rcon[i] = x << 24;
+                       x = mul2(x);
+               }
+
+               /*
+                * Generate the map x -> 3^x in GF(2^8). "3" happens to
+                * be a generator for GF(2^8)*, so we get all 255 non-zero
+                * elements.
+                */
+               uint[] pow3 = new uint[255];
+               x = 1;
+               for (int i = 0; i < 255; i ++) {
+                       pow3[i] = x;
+                       x ^= mul2(x);
+               }
+
+               /*
+                * Compute the log3 map 3^x -> x that maps any non-zero
+                * element in GF(2^8) to its logarithm in base 3 (in the
+                * 0..254 range).
+                */
+               int[] log3 = new int[256];
+               for (int i = 0; i < 255; i ++) {
+                       log3[pow3[i]] = i;
+               }
+
+               /*
+                * Compute the S-box.
+                */
+               S = new uint[256];
+               S[0] = aff(0);
+               S[1] = aff(1);
+               for (uint y = 2; y < 0x100; y ++) {
+                       S[y] = aff(pow3[255 - log3[y]]);
+               }
+
+               /*
+                * Compute the inverse S-box (for decryption).
+                */
+               iS = new uint[256];
+               for (uint y = 0; y < 0x100; y ++) {
+                       iS[S[y]] = y;
+               }
+
+               /*
+                * The Ssm*[] arrays combine SubBytes() and MixColumns():
+                * SsmX[v] is the effect of byte of value v when appearing
+                * on row X.
+                *
+                * The iSsm*[] arrays similarly combine InvSubBytes() and
+                * InvMixColumns(), for decryption.
+                */
+               Ssm0 = new uint[256];
+               Ssm1 = new uint[256];
+               Ssm2 = new uint[256];
+               Ssm3 = new uint[256];
+               iSsm0 = new uint[256];
+               iSsm1 = new uint[256];
+               iSsm2 = new uint[256];
+               iSsm3 = new uint[256];
+               for (uint p = 0; p < 0x100; p ++) {
+                       uint q = S[p];
+                       Ssm0[p] = (mul2(q) << 24)
+                               | (q << 16)
+                               | (q << 8)
+                               | mul3(q);
+                       Ssm1[p] = (mul3(q) << 24)
+                               | (mul2(q) << 16)
+                               | (q << 8)
+                               | q;
+                       Ssm2[p] = (q << 24)
+                               | (mul3(q) << 16)
+                               | (mul2(q) << 8)
+                               | q;
+                       Ssm3[p] = (q << 24)
+                               | (q << 16)
+                               | (mul3(q) << 8)
+                               | mul2(q);
+                       q = iS[p];
+                       iSsm0[p] = (mule(q) << 24)
+                               | (mul9(q) << 16)
+                               | (muld(q) << 8)
+                               | mulb(q);
+                       iSsm1[p] = (mulb(q) << 24)
+                               | (mule(q) << 16)
+                               | (mul9(q) << 8)
+                               | muld(q);
+                       iSsm2[p] = (muld(q) << 24)
+                               | (mulb(q) << 16)
+                               | (mule(q) << 8)
+                               | mul9(q);
+                       iSsm3[p] = (mul9(q) << 24)
+                               | (muld(q) << 16)
+                               | (mulb(q) << 8)
+                               | mule(q);
+               }
+       }
+}
+
+}
diff --git a/Crypto/BigInt.cs b/Crypto/BigInt.cs
new file mode 100644 (file)
index 0000000..893aa7c
--- /dev/null
@@ -0,0 +1,580 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Helper methods for handling big integers.
+ */
+
+static class BigInt {
+
+       /* 
+        * Normalize a big integer to its minimal size encoding
+        * (unsigned big-endian). This function always returns
+        * a new, fresh array.
+        */
+       internal static byte[] NormalizeBE(byte[] val)
+       {
+               return NormalizeBE(val, false);
+       }
+
+       /*
+        * Normalize a big integer to its minimal size encoding
+        * (big-endian). If 'signed' is true, then room for a sign
+        * bit (of value 0) is kept. Note that even if 'signed' is
+        * true, the source array is assumed positive (i.e. unsigned).
+        * This function always returns a new, fresh array.
+        */
+       internal static byte[] NormalizeBE(byte[] val, bool signed)
+       {
+               int n = val.Length;
+               int i = 0;
+               while (i < n && val[i] == 0) {
+                       i ++;
+               }
+               if (signed && (i == n || val[i] >= 0x80)) {
+                       i --;
+               }
+               byte[] nval = new byte[n - i];
+               if (i < 0) {
+                       Array.Copy(val, 0, nval, 1, n);
+               } else {
+                       Array.Copy(val, i, nval, 0, n - i);
+               }
+               return nval;
+       }
+
+       /*
+        * Compute the exact bit length of an integer (unsigned big-endian
+        * encoding).
+        */
+       internal static int BitLength(byte[] val)
+       {
+               return BitLength(val, 0, val.Length);
+       }
+
+       /*
+        * Compute the exact bit length of an integer (unsigned big-endian
+        * encoding).
+        */
+       internal static int BitLength(byte[] val, int off, int len)
+       {
+               int tlen = 0;
+               uint hb = 0;
+               uint nf = ~(uint)0;
+               for (int i = 0; i < len; i ++) {
+                       int b = (int)(val[off + i] & nf);
+                       uint bnz = nf & (uint)((b | -b) >> 31);
+                       tlen |= (int)bnz & (len - i);
+                       hb |= bnz & (uint)b;
+                       nf &= ~bnz;
+               }
+               return (tlen << 3) - 8 + BitLength(hb);
+       }
+
+       /*
+        * Compute the exact bit length of an integer (in a 32-bit word).
+        */
+       internal static int BitLength(uint w)
+       {
+               int bitLen = 0;
+               for (int f = 16; f > 0; f >>= 1) {
+                       uint nw = w >> f;
+                       int x = (int)nw;
+                       uint ctl = (uint)((x | -x) >> 31);
+                       w = (w & ~ctl) | (nw & ctl);
+                       bitLen += f & (int)ctl;
+               }
+               return bitLen + (int)w;
+       }
+
+       /*
+        * Compute a simple hashcode on an integer. The returned value
+        * depends on the numerical value (assuming that the array is
+        * in unsigned big-endian representation) but not on the presence
+        * of leading zeros. The memory access pattern of this method
+        * depends only on the length of the array x.
+        */
+       public static uint HashInt(byte[] x)
+       {
+               /*
+                * We simply compute x modulo 4294967291 (0xFFFFFFFB),
+                * which is a prime number. The value is injected byte
+                * by byte, and we keep the running state on two words
+                * (16 bits per word, but we allow a few extra bits of
+                * carry).
+                *
+                * For all iterations, "hi" contains at most 16 bits,
+                * and "lo" is less than 16*2^16 (i.e. it fits on 20 bits).
+                */
+               uint hi = 0, lo = 0;
+               for (int i = 0; i < x.Length; i ++) {
+                       hi = (hi << 8) + (lo >> 8);
+                       lo = (lo & 0xFF) << 8;
+                       lo += (uint)5 * (hi >> 16) + (uint)x[i];
+                       hi &= 0xFFFF;
+               }
+
+               /*
+                * Final reduction. We first propagate the extra bits
+                * from the low word, which may induce one extra bit
+                * on the high word, which we propagate back.
+                */
+               hi += (lo >> 16);
+               lo &= 0xFFFF;
+               lo += (uint)5 * (hi >> 16);
+               hi &= 0xFFFF;
+
+               /*
+                * If we have extra bits at that point, then this means
+                * that adding a 4-bits-or-less value to "hi" implied
+                * a carry, so now "hi" is small and the addition below
+                * won't imply a carry.
+                */
+               hi += (lo >> 16);
+               lo &= 0xFFFF;
+
+               /*
+                * At that point, value is on 32 bits. We want to do a
+                * final reduction for 0xFFFFFFFB..0xFFFFFFFF. Value is
+                * in this range if and only if 'hi' is 0xFFFF and 'lo'
+                * is at least 0xFFFB.
+                */
+               int z = (int)(((hi + 1) >> 16) | ((lo + 5) >> 16));
+               return (hi << 16) + lo + ((uint)5 & (uint)((z | -z) >> 31));
+       }
+
+       /*
+        * Add two integers together. The two operands a[] and b[]
+        * use big-endian encoding. The returned product array is newly
+        * allocated and normalized. The operands are not modified. The
+        * operands need not be normalized and may be the same array.
+        */
+       public static byte[] Add(byte[] a, byte[] b)
+       {
+               int aLen = a.Length;
+               int bLen = b.Length;
+               int xLen = Math.Max(aLen, bLen) + 1;
+               byte[] x = new byte[xLen];
+               int cc = 0;
+               for (int i = 0; i < xLen; i ++) {
+                       int wa = (i < aLen) ? (int)a[aLen - 1 - i] : 0;
+                       int wb = (i < bLen) ? (int)b[bLen - 1 - i] : 0;
+                       int wx = wa + wb + cc;
+                       x[xLen - 1 - i] = (byte)wx;
+                       cc = wx >> 8;
+               }
+               return NormalizeBE(x);
+       }
+
+       /*
+        * Subtract integer b[] from integer a[]. Both operands use
+        * big-endian encoding. If b[] turns out to be greater than a[],
+        * then this method returns null.
+        */
+       public static byte[] Sub(byte[] a, byte[] b)
+       {
+               int aLen = a.Length;
+               int bLen = b.Length;
+               int xLen = Math.Max(aLen, bLen);
+               byte[] x = new byte[aLen];
+               int cc = 0;
+               for (int i = 0; i < xLen; i ++) {
+                       int wa = (i < aLen) ? (int)a[aLen - 1 - i] : 0;
+                       int wb = (i < bLen) ? (int)b[bLen - 1 - i] : 0;
+                       int wx = wa - wb - cc;
+                       x[xLen - 1 - i] = (byte)wx;
+                       cc = (wx >> 8) & 1;
+               }
+               if (cc != 0) {
+                       return null;
+               }
+               return NormalizeBE(x);
+       }
+
+       /*
+        * Multiply two integers together. The two operands a[] and b[]
+        * use big-endian encoding. The returned product array is newly
+        * allocated and normalized. The operands are not modified. The
+        * operands need not be normalized and may be the same array.
+        *
+        * The two source operands MUST NOT have length larger than
+        * 32767 bytes.
+        */
+       public static byte[] Mul(byte[] a, byte[] b)
+       {
+               a = NormalizeBE(a);
+               b = NormalizeBE(b);
+               if (a.Length > 32767 || b.Length > 32767) {
+                       throw new CryptoException(
+                               "Operands too large for multiplication");
+               }
+               int aLen = a.Length;
+               int bLen = b.Length;
+               int xLen = aLen + bLen;
+               uint[] x = new uint[xLen];
+               for (int i = 0; i < aLen; i ++) {
+                       uint u = (uint)a[aLen - 1 - i];
+                       for (int j = 0; j < bLen; j ++) {
+                               x[i + j] += u * (uint)b[bLen - 1 - j];
+                       }
+               }
+               byte[] y = new byte[xLen];
+               uint cc = 0;
+               for (int i = 0; i < xLen; i ++) {
+                       uint w = x[i] + cc;
+                       y[xLen - 1 - i] = (byte)w;
+                       cc = w >> 8;
+               }
+               if (cc != 0) {
+                       throw new CryptoException(
+                               "Multiplication: internal error");
+               }
+               return NormalizeBE(y);
+       }
+
+       /*
+        * Compare two integers (unsigned, big-endian). Returned value
+        * is -1, 0 or 1, depending on whether a[] is lower than, equal
+        * to, or greater then b[]. a[] and b[] may have distinct sizes.
+        *
+        * Memory access pattern depends on the most significant index
+        * (i.e. lowest index, since we use big-endian convention) for
+        * which the two values differ.
+        */
+       public static int Compare(byte[] a, byte[] b)
+       {
+               int na = a.Length;
+               int nb = b.Length;
+               for (int i = Math.Max(a.Length, b.Length); i > 0; i --) {
+                       byte xa = (i > na) ? (byte)0x00 : a[na - i];
+                       byte xb = (i > nb) ? (byte)0x00 : b[nb - i];
+                       if (xa != xb) {
+                               return xa < xb ? -1 : 1;
+                       }
+               }
+               return 0;
+       }
+
+       /*
+        * Compare two integers (unsigned, big-endian). Returned value
+        * is -1, 0 or 1, depending on whether a[] is lower than, equal
+        * to, or greater then b[]. a[] and b[] may have distinct sizes.
+        *
+        * This method's memory access pattern is independent of the
+        * contents of the a[] and b[] arrays.
+        */
+       public static int CompareCT(byte[] a, byte[] b)
+       {
+               int na = a.Length;
+               int nb = b.Length;
+               uint lt = 0;
+               uint gt = 0;
+               for (int i = Math.Max(a.Length, b.Length); i > 0; i --) {
+                       int xa = (i > na) ? 0 : a[na - i];
+                       int xb = (i > nb) ? 0 : b[nb - i];
+                       lt |= (uint)((xa - xb) >> 31) & ~(lt | gt);
+                       gt |= (uint)((xb - xa) >> 31) & ~(lt | gt);
+               }
+               return (int)lt | -(int)gt;
+       }
+
+       /*
+        * Check whether an integer (unsigned, big-endian) is zero.
+        */
+       public static bool IsZero(byte[] x)
+       {
+               return IsZeroCT(x) != 0;
+       }
+
+       /*
+        * Check whether an integer (unsigned, big-endian) is zero.
+        * Memory access pattern depends only on the length of x[].
+        * Returned value is 0xFFFFFFFF is the value is zero, 0x00000000
+        * otherwise.
+        */
+       public static uint IsZeroCT(byte[] x)
+       {
+               int z = 0;
+               for (int i = 0; i < x.Length; i ++) {
+                       z |= x[i];
+               }
+               return ~(uint)((z | -z) >> 31);
+       }
+
+       /*
+        * Check whether an integer (unsigned, big-endian) is one.
+        */
+       public static bool IsOne(byte[] x)
+       {
+               return IsOneCT(x) != 0;
+       }
+
+       /*
+        * Check whether an integer (unsigned, big-endian) is one.
+        * Memory access pattern depends only on the length of x[].
+        * Returned value is 0xFFFFFFFF is the value is one, 0x00000000
+        * otherwise.
+        */
+       public static uint IsOneCT(byte[] x)
+       {
+               int n = x.Length;
+               if (n == 0) {
+                       return 0x00000000;
+               }
+               int z = 0;
+               for (int i = 0; i < n - 1; i ++) {
+                       z |= x[i];
+               }
+               z |= x[n - 1] - 1;
+               return ~(uint)((z | -z) >> 31);
+       }
+
+       /*
+        * Check whether the provided integer is odd (the source integer
+        * is in unsigned big-endian notation).
+        */
+       public static bool IsOdd(byte[] x)
+       {
+               return x.Length > 0 && (x[x.Length - 1] & 0x01) != 0;
+       }
+
+       /*
+        * Compute a modular exponentiation (x^e mod n). Conditions:
+        * -- x[], e[] and n[] use big-endian encoding.
+        * -- x[] must be numerically smaller than n[].
+        * -- n[] must be odd.
+        * Result is returned as a newly allocated array of bytes of
+        * the same length as n[].
+        */
+       public static byte[] ModPow(byte[] x, byte[] e, byte[] n)
+       {
+               ModInt mx = new ModInt(n);
+               mx.Decode(x);
+               mx.Pow(e);
+               return mx.Encode();
+       }
+
+       /*
+        * Create a new random integer, chosen uniformly among integers
+        * modulo the provided max[].
+        */
+       public static byte[] RandInt(byte[] max)
+       {
+               return RandInt(max, false);
+       }
+
+       /*
+        * Create a new random integer, chosen uniformly among non-zero
+        * integers modulo the provided max[].
+        */
+       public static byte[] RandIntNZ(byte[] max)
+       {
+               return RandInt(max, true);
+       }
+
+       static byte[] RandInt(byte[] max, bool notZero)
+       {
+               int mlen = BitLength(max);
+               if (mlen == 0 || (notZero && mlen == 1)) {
+                       throw new CryptoException(
+                               "Null maximum for random generation");
+               }
+               byte[] x = new byte[(mlen + 7) >> 3];
+               byte hm = (byte)(0xFF >> ((8 - mlen) & 7));
+               for (;;) {
+                       RNG.GetBytes(x);
+                       x[0] &= hm;
+                       if (notZero && IsZero(x)) {
+                               continue;
+                       }
+                       if (CompareCT(x, max) >= 0) {
+                               continue;
+                       }
+                       return x;
+               }
+       }
+
+       /*
+        * Create a new random prime with a specific length (in bits). The
+        * returned prime will have its two top bits set, _and_ its two
+        * least significant bits set as well. The size parameter must be
+        * greater than or equal to 9 (that is, the unsigned encoding of
+        * the prime will need at least two bytes).
+        */
+       public static byte[] RandPrime(int size)
+       {
+               if (size < 9) {
+                       throw new CryptoException(
+                               "Invalid size for prime generation");
+               }
+               int len = (size + 7) >> 3;
+               byte[] buf = new byte[len];
+               int hm1 = 0xFFFF >> ((len << 3) - size);
+               int hm2 = 0xC000 >> ((len << 3) - size);
+               for (;;) {
+                       RNG.GetBytes(buf);
+                       buf[len - 1] |= (byte)0x03;
+                       int x = (buf[0] << 8) | buf[1];
+                       x &= hm1;
+                       x |= hm2;
+                       buf[0] = (byte)(x >> 8);
+                       buf[1] = (byte)x;
+                       if (IsPrime(buf)) {
+                               return buf;
+                       }
+               }
+       }
+
+       /*
+        * A bit-field for primes in the 0..255 range.
+        */
+       static uint[] SMALL_PRIMES_BF = {
+               0xA08A28AC, 0x28208A20, 0x02088288, 0x800228A2,
+               0x20A00A08, 0x80282088, 0x800800A2, 0x08028228
+       };
+
+       static bool IsSmallPrime(int x)
+       {
+               if (x < 2 || x >= 256) {
+                       return false;
+               }
+               return ((SMALL_PRIMES_BF[x >> 5] >> (x & 31)) & (uint)1) != 0;
+       }
+
+       /*
+        * Test an integer for primality. This function runs up to 50
+        * Miller-Rabin rounds, which is a lot of overkill but ensures
+        * that non-primes will be reliably detected (with overwhelming
+        * probability) even with maliciously crafted inputs. "Normal"
+        * non-primes will be detected most of the time at the first
+        * iteration.
+        *
+        * This function is not constant-time.
+        */
+       public static bool IsPrime(byte[] x)
+       {
+               x = NormalizeBE(x);
+
+               /*
+                * Handle easy cases:
+                *   0 is not prime
+                *   small primes (one byte) are known in a constant bit-field
+                *   even numbers (larger than one byte) are non-primes
+                */
+               if (x.Length == 0) {
+                       return false;
+               }
+               if (x.Length == 1) {
+                       return IsSmallPrime(x[0]);
+               }
+               if ((x[x.Length - 1] & 0x01) == 0) {
+                       return false;
+               }
+
+               /*
+                * Perform some trial divisions by small primes.
+                */
+               for (int sp = 3; sp < 256; sp += 2) {
+                       if (!IsSmallPrime(sp)) {
+                               continue;
+                       }
+                       int z = 0;
+                       foreach (byte b in x) {
+                               z = ((z << 8) + b) % sp;
+                       }
+                       if (z == 0) {
+                               return false;
+                       }
+               }
+
+               /*
+                * Run some Miller-Rabin rounds. We use as basis random
+                * integers that are one byte smaller than the modulus.
+                */
+               ModInt xm1 = new ModInt(x);
+               ModInt y = xm1.Dup();
+               y.Set(1);
+               xm1.Sub(y);
+               byte[] e = xm1.Encode();
+               ModInt a = new ModInt(x);
+               byte[] buf = new byte[x.Length - 1];
+               for (int i = 0; i < 50; i ++) {
+                       RNG.GetBytes(buf);
+                       a.Decode(buf);
+                       a.Pow(e);
+                       if (!a.IsOne) {
+                               return false;
+                       }
+               }
+               return true;
+       }
+
+       /*
+        * Right-shift an array of bytes by some bits. The bit count MUST
+        * be positive or zero. Extra bits are dropped on the right, and
+        * left positions are filled with zeros.
+        */
+       public static void RShift(byte[] buf, int numBits)
+       {
+               RShift(buf, 0, buf.Length, numBits);
+       }
+
+       /*
+        * Right-shift an array of bytes by some bits. The bit count MUST
+        * be positive or zero. Extra bits are dropped on the right, and
+        * left positions are filled with zeros.
+        */
+       public static void RShift(byte[] buf, int off, int len, int numBits)
+       {
+               if (numBits >= 8) {
+                       int zlen = numBits >> 3;
+                       if (zlen >= len) {
+                               for (int i = 0; i < len; i ++) {
+                                       buf[off + i] = 0;
+                               }
+                               return;
+                       }
+                       Array.Copy(buf, off, buf, off + zlen, len - zlen);
+                       for (int i = 0; i < zlen; i ++) {
+                               buf[off + i] = 0;
+                       }
+                       off += zlen;
+                       len -= zlen;
+                       numBits &= 7;
+               }
+
+               int cc = 0;
+               for (int i = 0; i < len; i ++) {
+                       int x = buf[off + i];
+                       buf[off + i] = (byte)((x >> numBits) + cc);
+                       cc = x << (8 - numBits);
+               }
+       }
+}
+
+}
diff --git a/Crypto/BlockCipherCore.cs b/Crypto/BlockCipherCore.cs
new file mode 100644 (file)
index 0000000..2f1b761
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class is a convenient base class for implementations of
+ * IBlockCipher. Block cipher implementations must implement:
+ *    int BlockSize { get; }
+ *    void SetKey(byte[] key, int off, int len)
+ *    void BlockEncrypt(byte[] data, int off)
+ *    void BlockDecrypt(byte[] data, int off)
+ *
+ * Note that 'BlockSize' is invoked from the constructor of this class.
+ *
+ * Implementations MAY also override the default implementations of:
+ *    void CBCEncrypt(byte[] iv, byte[] data)
+ *    void CBCEncrypt(byte[] iv, byte[] data, int off, int len)
+ *    void CBCDecrypt(byte[] iv, byte[] data)
+ *    void CBCDecrypt(byte[] iv, byte[] data, int off, int len)
+ *    uint CTRRun(byte[] iv, uint cc, byte[] data)
+ *    uint CTRRun(byte[] iv, uint cc, byte[] data, int off, int len)
+ * Note that CBCEncrypt(byte[],byte[]) (respectively
+ * CBCDecrypt(byte[],byte[]) and CTRRun(byte[],uint,byte[])) simply
+ * calls CBCEncrypt(byte[],byte[],int,int) (respectively
+ * CBCDecrypt(byte[],byte[],int,int) and
+ * CTRRun(byte[],uint,byte[],int,int)) so implementations who wish to
+ * override these methods may content themselves with overriding the two
+ * methods with the "off" and "len" extra parameters.
+ */
+
+public abstract class BlockCipherCore : IBlockCipher {
+
+       byte[] tmp;
+
+       /*
+        * This constructor invokes 'BlockSize'.
+        */
+       public BlockCipherCore()
+       {
+               tmp = new byte[BlockSize];
+       }
+
+       /* see IBlockCipher */
+       public abstract int BlockSize { get; }
+
+       /*
+        * This method is implemented by calling SetKey(byte[],int,int).
+        */
+       public virtual void SetKey(byte[] key)
+       {
+               SetKey(key, 0, key.Length);
+       }
+
+       /* see IBlockCipher */
+       public abstract void SetKey(byte[] key, int off, int len);
+
+       /*
+        * This method is implemented by calling BlockEncrypt(byte[],int).
+        */
+       public virtual void BlockEncrypt(byte[] buf)
+       {
+               BlockEncrypt(buf, 0);
+       }
+
+       /* see IBlockCipher */
+       public abstract void BlockEncrypt(byte[] data, int off);
+
+       /*
+        * This method is implemented by calling BlockDecrypt(byte[],int).
+        */
+       public virtual void BlockDecrypt(byte[] buf)
+       {
+               BlockDecrypt(buf, 0);
+       }
+
+       /* see IBlockCipher */
+       public abstract void BlockDecrypt(byte[] data, int off);
+
+       /*
+        * This method is implemented by calling
+        * CBCEncrypt(byte[],byte[],int,int).
+        */
+       public virtual void CBCEncrypt(byte[] iv, byte[] data)
+       {
+               CBCEncrypt(iv, data, 0, data.Length);
+       }
+
+       /* see IBlockCipher */
+       public virtual void CBCEncrypt(
+               byte[] iv, byte[] data, int off, int len)
+       {
+               int blen = BlockSize;
+               if (iv.Length != blen) {
+                       throw new CryptoException("wrong IV length");
+               }
+               if (len >= blen) {
+                       for (int i = 0; i < blen; i ++) {
+                               data[off + i] ^= iv[i];
+                       }
+                       BlockEncrypt(data, off);
+                       off += blen;
+                       len -= blen;
+                       while (len >= blen) {
+                               for (int i = 0; i < blen; i ++) {
+                                       data[off + i] ^= data[off + i - blen];
+                               }
+                               BlockEncrypt(data, off);
+                               off += blen;
+                               len -= blen;
+                       }
+               }
+               if (len != 0) {
+                       throw new CryptoException("data length is not"
+                               + " multiple of the block size");
+               }
+       }
+
+       /*
+        * This method is implemented by calling
+        * CBCDecrypt(byte[],byte[],int,int).
+        */
+       public virtual void CBCDecrypt(byte[] iv, byte[] data)
+       {
+               CBCDecrypt(iv, data, 0, data.Length);
+       }
+
+       /* see IBlockCipher */
+       public virtual void CBCDecrypt(
+               byte[] iv, byte[] data, int off, int len)
+       {
+               int blen = BlockSize;
+               if (iv.Length != blen) {
+                       throw new CryptoException("wrong IV length");
+               }
+               int dblen = blen << 1;
+               off += len;
+               while (len >= dblen) {
+                       off -= blen;
+                       BlockDecrypt(data, off);
+                       for (int i = 0; i < blen; i ++) {
+                               data[off + i] ^= data[off + i - blen];
+                       }
+                       len -= blen;
+               }
+               if (len >= blen) {
+                       off -= blen;
+                       BlockDecrypt(data, off);
+                       for (int i = 0; i < blen; i ++) {
+                               data[off + i] ^= iv[i];
+                       }
+                       len -= blen;
+               }
+               if (len != 0) {
+                       throw new CryptoException("data length is not"
+                               + " multiple of the block size");
+               }
+       }
+
+       /*
+        * This method is implemented by calling
+        * CTRRun(byte[],uint,byte[],int,int).
+        */
+       public virtual uint CTRRun(byte[] iv, uint cc, byte[] data)
+       {
+               return CTRRun(iv, cc, data, 0, data.Length);
+       }
+
+       /* see IBlockCipher */
+       public virtual uint CTRRun(
+               byte[] iv, uint cc, byte[] data, int off, int len)
+       {
+               int blen = BlockSize;
+               if (iv.Length != blen - 4) {
+                       throw new CryptoException("wrong IV length");
+               }
+               while (len > 0) {
+                       Array.Copy(iv, 0, tmp, 0, blen - 4);
+                       tmp[blen - 4] = (byte)(cc >> 24);
+                       tmp[blen - 3] = (byte)(cc >> 16);
+                       tmp[blen - 2] = (byte)(cc >> 8);
+                       tmp[blen - 1] = (byte)cc;
+                       BlockEncrypt(tmp, 0);
+                       int clen = Math.Min(blen, len);
+                       for (int i = 0; i < clen; i ++) {
+                               data[off + i] ^= tmp[i];
+                       }
+                       off += clen;
+                       len -= clen;
+                       cc ++;
+               }
+               return cc;
+       }
+
+       /* see IBlockCipher */
+       public abstract IBlockCipher Dup();
+}
+
+}
diff --git a/Crypto/ChaCha20.cs b/Crypto/ChaCha20.cs
new file mode 100644 (file)
index 0000000..52e1fd7
--- /dev/null
@@ -0,0 +1,311 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * ChaCha20 implementation. 
+ */
+
+public sealed class ChaCha20 {
+
+       uint k0, k1, k2, k3, k4, k5, k6, k7;
+
+       const uint CW0 = 0x61707865;
+       const uint CW1 = 0x3320646E;
+       const uint CW2 = 0x79622D32;
+       const uint CW3 = 0x6B206574;
+
+       /*
+        * Initialize a new instance.
+        */
+       public ChaCha20()
+       {
+       }
+
+       /*
+        * Set the key (32 bytes).
+        */
+       public void SetKey(byte[] key)
+       {
+               SetKey(key, 0, key.Length);
+       }
+
+       /*
+        * Set the key (32 bytes).
+        */
+       public void SetKey(byte[] key, int off, int len)
+       {
+               if (len != 32) {
+                       throw new ArgumentException(
+                               "bad ChaCha20 key length: " + len);
+               }
+               k0 = Dec32le(key, off +  0);
+               k1 = Dec32le(key, off +  4);
+               k2 = Dec32le(key, off +  8);
+               k3 = Dec32le(key, off + 12);
+               k4 = Dec32le(key, off + 16);
+               k5 = Dec32le(key, off + 20);
+               k6 = Dec32le(key, off + 24);
+               k7 = Dec32le(key, off + 28);
+       }
+
+       /*
+        * Encrypt (or decrypt) some bytes. The current block counter
+        * is provided, and the new block counter value is returned.
+        * Each block is 64 bytes; if the data length is not a multiple
+        * of 64, then the extra bytes from the last block are dropped;
+        * thus, a long stream of bytes can be encrypted or decrypted
+        * in several calls, as long as all calls (except possibly the
+        * last) provide a length that is a multiple of 64.
+        *
+        * IV must be exactly 12 bytes.
+        */
+       public uint Run(byte[] iv, uint cc, byte[] data)
+       {
+               return Run(iv, cc, data, 0, data.Length);
+       }
+
+       /*
+        * Encrypt (or decrypt) some bytes. The current block counter
+        * is provided, and the new block counter value is returned.
+        * Each block is 64 bytes; if the data length is not a multiple
+        * of 64, then the extra bytes from the last block are dropped;
+        * thus, a long stream of bytes can be encrypted or decrypted
+        * in several calls, as long as all calls (except possibly the
+        * last) provide a length that is a multiple of 64.
+        *
+        * IV must be exactly 12 bytes.
+        */
+       public uint Run(byte[] iv, uint cc, byte[] data, int off, int len)
+       {
+               uint iv0 = Dec32le(iv, 0);
+               uint iv1 = Dec32le(iv, 4);
+               uint iv2 = Dec32le(iv, 8);
+               while (len > 0) {
+                       uint s0, s1, s2, s3, s4, s5, s6, s7;
+                       uint s8, s9, sA, sB, sC, sD, sE, sF;
+
+                       s0 = CW0;
+                       s1 = CW1;
+                       s2 = CW2;
+                       s3 = CW3;
+                       s4 = k0;
+                       s5 = k1;
+                       s6 = k2;
+                       s7 = k3;
+                       s8 = k4;
+                       s9 = k5;
+                       sA = k6;
+                       sB = k7;
+                       sC = cc;
+                       sD = iv0;
+                       sE = iv1;
+                       sF = iv2;
+
+                       for (int i = 0; i < 10; i ++) {
+                               s0 += s4;
+                               sC ^= s0;
+                               sC = (sC << 16) | (sC >> 16);
+                               s8 += sC;
+                               s4 ^= s8;
+                               s4 = (s4 << 12) | (s4 >> 20);
+                               s0 += s4;
+                               sC ^= s0;
+                               sC = (sC <<  8) | (sC >> 24);
+                               s8 += sC;
+                               s4 ^= s8;
+                               s4 = (s4 <<  7) | (s4 >> 25);
+
+                               s1 += s5;
+                               sD ^= s1;
+                               sD = (sD << 16) | (sD >> 16);
+                               s9 += sD;
+                               s5 ^= s9;
+                               s5 = (s5 << 12) | (s5 >> 20);
+                               s1 += s5;
+                               sD ^= s1;
+                               sD = (sD <<  8) | (sD >> 24);
+                               s9 += sD;
+                               s5 ^= s9;
+                               s5 = (s5 <<  7) | (s5 >> 25);
+
+                               s2 += s6;
+                               sE ^= s2;
+                               sE = (sE << 16) | (sE >> 16);
+                               sA += sE;
+                               s6 ^= sA;
+                               s6 = (s6 << 12) | (s6 >> 20);
+                               s2 += s6;
+                               sE ^= s2;
+                               sE = (sE <<  8) | (sE >> 24);
+                               sA += sE;
+                               s6 ^= sA;
+                               s6 = (s6 <<  7) | (s6 >> 25);
+
+                               s3 += s7;
+                               sF ^= s3;
+                               sF = (sF << 16) | (sF >> 16);
+                               sB += sF;
+                               s7 ^= sB;
+                               s7 = (s7 << 12) | (s7 >> 20);
+                               s3 += s7;
+                               sF ^= s3;
+                               sF = (sF <<  8) | (sF >> 24);
+                               sB += sF;
+                               s7 ^= sB;
+                               s7 = (s7 <<  7) | (s7 >> 25);
+
+                               s0 += s5;
+                               sF ^= s0;
+                               sF = (sF << 16) | (sF >> 16);
+                               sA += sF;
+                               s5 ^= sA;
+                               s5 = (s5 << 12) | (s5 >> 20);
+                               s0 += s5;
+                               sF ^= s0;
+                               sF = (sF <<  8) | (sF >> 24);
+                               sA += sF;
+                               s5 ^= sA;
+                               s5 = (s5 <<  7) | (s5 >> 25);
+
+                               s1 += s6;
+                               sC ^= s1;
+                               sC = (sC << 16) | (sC >> 16);
+                               sB += sC;
+                               s6 ^= sB;
+                               s6 = (s6 << 12) | (s6 >> 20);
+                               s1 += s6;
+                               sC ^= s1;
+                               sC = (sC <<  8) | (sC >> 24);
+                               sB += sC;
+                               s6 ^= sB;
+                               s6 = (s6 <<  7) | (s6 >> 25);
+
+                               s2 += s7;
+                               sD ^= s2;
+                               sD = (sD << 16) | (sD >> 16);
+                               s8 += sD;
+                               s7 ^= s8;
+                               s7 = (s7 << 12) | (s7 >> 20);
+                               s2 += s7;
+                               sD ^= s2;
+                               sD = (sD <<  8) | (sD >> 24);
+                               s8 += sD;
+                               s7 ^= s8;
+                               s7 = (s7 <<  7) | (s7 >> 25);
+
+                               s3 += s4;
+                               sE ^= s3;
+                               sE = (sE << 16) | (sE >> 16);
+                               s9 += sE;
+                               s4 ^= s9;
+                               s4 = (s4 << 12) | (s4 >> 20);
+                               s3 += s4;
+                               sE ^= s3;
+                               sE = (sE <<  8) | (sE >> 24);
+                               s9 += sE;
+                               s4 ^= s9;
+                               s4 = (s4 <<  7) | (s4 >> 25);
+                       }
+
+                       s0 += CW0;
+                       s1 += CW1;
+                       s2 += CW2;
+                       s3 += CW3;
+                       s4 += k0;
+                       s5 += k1;
+                       s6 += k2;
+                       s7 += k3;
+                       s8 += k4;
+                       s9 += k5;
+                       sA += k6;
+                       sB += k7;
+                       sC += cc;
+                       sD += iv0;
+                       sE += iv1;
+                       sF += iv2;
+
+                       int limit = off + len;
+                       Xor32le(s0, data, off +  0, limit);
+                       Xor32le(s1, data, off +  4, limit);
+                       Xor32le(s2, data, off +  8, limit);
+                       Xor32le(s3, data, off + 12, limit);
+                       Xor32le(s4, data, off + 16, limit);
+                       Xor32le(s5, data, off + 20, limit);
+                       Xor32le(s6, data, off + 24, limit);
+                       Xor32le(s7, data, off + 28, limit);
+                       Xor32le(s8, data, off + 32, limit);
+                       Xor32le(s9, data, off + 36, limit);
+                       Xor32le(sA, data, off + 40, limit);
+                       Xor32le(sB, data, off + 44, limit);
+                       Xor32le(sC, data, off + 48, limit);
+                       Xor32le(sD, data, off + 52, limit);
+                       Xor32le(sE, data, off + 56, limit);
+                       Xor32le(sF, data, off + 60, limit);
+
+                       off += 64;
+                       len -= 64;
+                       cc ++;
+               }
+               return cc;
+       }
+
+       static uint Dec32le(byte[] buf, int off)
+       {
+               return (uint)buf[off]
+                       | ((uint)buf[off + 1] << 8)
+                       | ((uint)buf[off + 2] << 16)
+                       | ((uint)buf[off + 3] << 24);
+       }
+
+       static void Xor32le(uint x, byte[] buf, int off, int limit)
+       {
+               if (off + 4 <= limit) {
+                       buf[off] ^= (byte)x;
+                       buf[off + 1] ^= (byte)(x >> 8);
+                       buf[off + 2] ^= (byte)(x >> 16);
+                       buf[off + 3] ^= (byte)(x >> 24);
+               } else {
+                       if (off + 2 <= limit) {
+                               if (off + 3 <= limit) {
+                                       buf[off] ^= (byte)x;
+                                       buf[off + 1] ^= (byte)(x >> 8);
+                                       buf[off + 2] ^= (byte)(x >> 16);
+                               } else {
+                                       buf[off] ^= (byte)x;
+                                       buf[off + 1] ^= (byte)(x >> 8);
+                               }
+                       } else {
+                               if (off + 1 <= limit) {
+                                       buf[off] ^= (byte)x;
+                               }
+                       }
+               }
+       }
+}
+
+}
diff --git a/Crypto/CryptoException.cs b/Crypto/CryptoException.cs
new file mode 100644 (file)
index 0000000..5ce93c6
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * A basic exception class, to be thrown when a cryptographic
+ * algorithm encounters an unfixable issue.
+ */
+
+public class CryptoException : Exception {
+
+       public CryptoException(string msg) : base(msg)
+       {
+       }
+
+       public CryptoException(string msg, Exception e) : base(msg, e)
+       {
+       }
+}
+
+}
diff --git a/Crypto/DES.cs b/Crypto/DES.cs
new file mode 100644 (file)
index 0000000..3446d58
--- /dev/null
@@ -0,0 +1,492 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Perfunctory implementation of the Triple-DES block cipher (also works
+ * as "single DES" when used with an 8-byte DES key). It only processes
+ * single blocks, and does it "in place" (the input block is replaced
+ * with the output block in the same array). This code is a direct
+ * translation of the specification and is not optimized for speed.
+ *
+ * Supported key sizes are 8, 16 and 24 bytes. When an 8-byte key is
+ * used, this is the original DES (64-bit key, out of which 8 are
+ * unused, so the "true key size" is 56 bits). When a 16-byte or 24-byte
+ * key is used, this is Triple-DES (with a 16-byte key, the key is
+ * expanded to 24 bytes by reusing bytes 0..7 as bytes 16..23 of the
+ * key).
+ *
+ * Instances are not thread-safe; however, distinct threads may use
+ * distinct instances concurrently.
+ *
+ * Since instances are pure managed code, there is no need for explicit
+ * disposal after usage.
+ */
+
+public sealed class DES : BlockCipherCore {
+
+       ulong[] skey;
+       int rounds;
+
+       /*
+        * Initialize a new instance.
+        */
+       public DES()
+       {
+               skey = new ulong[48];
+       }
+
+       /* see IBlockCipher */
+       public override IBlockCipher Dup()
+       {
+               DES d = new DES();
+               Array.Copy(skey, 0, d.skey, 0, skey.Length);
+               d.rounds = rounds;
+               return d;
+       }
+
+       /*
+        * Get the block size in bytes (always 8).
+        */
+       public override int BlockSize {
+               get {
+                       return 8;
+               }
+       }
+
+       /*
+        * Set the key (8, 16 or 24 bytes).
+        */
+       public override void SetKey(byte[] key, int off, int len)
+       {
+               switch (len) {
+               case 8:
+                       KeySchedule(key, off, 0);
+                       rounds = 1;
+                       break;
+               case 16:
+                       KeySchedule(key, off, 0);
+                       KeySchedule(key, off + 8, 16);
+                       KeySchedule(key, off, 32);
+                       rounds = 3;
+                       break;
+               case 24:
+                       KeySchedule(key, off, 0);
+                       KeySchedule(key, off + 8, 16);
+                       KeySchedule(key, off + 16, 32);
+                       rounds = 3;
+                       break;
+               default:
+                       throw new ArgumentException(
+                               "bad DES/3DES key length: " + len);
+               }
+
+               /* 
+                * Inverse order of subkeys for the middle-DES (3DES
+                * uses the "EDE" configuration).
+                */
+               for (int j = 16; j < 24; j ++) {
+                       ulong w = skey[j];
+                       skey[j] = skey[47 - j];
+                       skey[47 - j] = w;
+               }
+       }
+
+       void KeySchedule(byte[] key, int off, int skeyOff)
+       {
+               ulong k = Dec64be(key, off);
+               k = Perm(tabPC1, k);
+               ulong kl = k >> 28;
+               ulong kr = k & 0x0FFFFFFF;
+               for (int i = 0; i < 16; i ++) {
+                       int r = rotK[i];
+                       kl = ((kl << r) | (kl >> (28 - r))) & 0x0FFFFFFF;
+                       kr = ((kr << r) | (kr >> (28 - r))) & 0x0FFFFFFF;
+                       skey[skeyOff + i] = Perm(tabPC2, (kl << 28) | kr);
+               }
+       }
+
+       /*
+        * Encrypt one block; the block consists in the 8 bytes beginning
+        * at offset 'off'. Other bytes in the array are unaltered.
+        */
+       public override void BlockEncrypt(byte[] buf, int off)
+       {
+               if (rounds == 0) {
+                       throw new Exception("no key provided");
+               }
+               ulong x = Dec64be(buf, off);
+               x = DoIP(x);
+               uint xl = (uint)(x >> 32);
+               uint xr = (uint)x;
+               for (int i = 0, k = 0; i < rounds; i ++) {
+                       xl ^= FConf(xr, skey[k ++]);
+                       xr ^= FConf(xl, skey[k ++]);
+                       xl ^= FConf(xr, skey[k ++]);
+                       xr ^= FConf(xl, skey[k ++]);
+                       xl ^= FConf(xr, skey[k ++]);
+                       xr ^= FConf(xl, skey[k ++]);
+                       xl ^= FConf(xr, skey[k ++]);
+                       xr ^= FConf(xl, skey[k ++]);
+                       xl ^= FConf(xr, skey[k ++]);
+                       xr ^= FConf(xl, skey[k ++]);
+                       xl ^= FConf(xr, skey[k ++]);
+                       xr ^= FConf(xl, skey[k ++]);
+                       xl ^= FConf(xr, skey[k ++]);
+                       xr ^= FConf(xl, skey[k ++]);
+                       xl ^= FConf(xr, skey[k ++]);
+                       uint tmp = xr ^ FConf(xl, skey[k ++]);
+                       xr = xl;
+                       xl = tmp;
+               }
+               x = ((ulong)xl << 32) | (ulong)xr;
+               x = DoIPInv(x);
+               Enc64be(x, buf, off);
+       }
+
+       /*
+        * Decrypt one block; the block consists in the 8 bytes beginning
+        * at offset 'off'. Other bytes in the array are unaltered.
+        */
+       public override void BlockDecrypt(byte[] buf, int off)
+       {
+               if (rounds == 0) {
+                       throw new Exception("no key provided");
+               }
+               ulong x = Dec64be(buf, off);
+               x = DoIP(x);
+               uint xl = (uint)(x >> 32);
+               uint xr = (uint)x;
+               for (int i = 0, k = rounds << 4; i < rounds; i ++) {
+                       xl ^= FConf(xr, skey[-- k]);
+                       xr ^= FConf(xl, skey[-- k]);
+                       xl ^= FConf(xr, skey[-- k]);
+                       xr ^= FConf(xl, skey[-- k]);
+                       xl ^= FConf(xr, skey[-- k]);
+                       xr ^= FConf(xl, skey[-- k]);
+                       xl ^= FConf(xr, skey[-- k]);
+                       xr ^= FConf(xl, skey[-- k]);
+                       xl ^= FConf(xr, skey[-- k]);
+                       xr ^= FConf(xl, skey[-- k]);
+                       xl ^= FConf(xr, skey[-- k]);
+                       xr ^= FConf(xl, skey[-- k]);
+                       xl ^= FConf(xr, skey[-- k]);
+                       xr ^= FConf(xl, skey[-- k]);
+                       xl ^= FConf(xr, skey[-- k]);
+                       uint tmp = xr ^ FConf(xl, skey[-- k]);
+                       xr = xl;
+                       xl = tmp;
+               }
+               x = ((ulong)xl << 32) | (ulong)xr;
+               x = DoIPInv(x);
+               Enc64be(x, buf, off);
+       }
+
+       /*
+        * Arrays below are extracted exactly from FIPS 46-3. They use
+        * the conventions defined in that document:
+        *
+        * -- Bits are numbered from 1, in the left-to-right order; in
+        *    a 64-bit integer, the most significant (leftmost) bit is 1,
+        *    while the least significant (rightmost) bit is 64.
+        *
+        * -- For each permutation (or extraction), the defined array
+        *    lists the source index of each bit.
+        *
+        * -- For the S-boxes, bits 1 (leftmost) and 6 (rightmost) select
+        *    the row, and bits 2 to 5 select the index within the row.
+        */
+
+       static uint[,] Sdef = {
+               {
+                       14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
+                       0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
+                       4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
+                       15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13
+               }, {
+                       15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
+                       3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
+                       0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
+                       13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9
+               }, {
+                       10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
+                       13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
+                       13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
+                       1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12
+               }, {
+                       7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
+                       13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
+                       10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
+                       3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14
+               }, {
+                       2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
+                       14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
+                       4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
+                       11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3
+               }, {
+                       12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
+                       10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
+                       9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
+                       4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13
+               }, {
+                       4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
+                       13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
+                       1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
+                       6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12
+               }, {
+                       13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
+                       1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
+                       7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
+                       2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11
+               }
+       };
+
+       static int[] defP = {
+               16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10,
+               2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25
+       };
+
+       static int[] defPC1 = {
+               57, 49, 41, 33, 25, 17, 9,
+               1, 58, 50, 42, 34, 26, 18,
+               10, 2, 59, 51, 43, 35, 27,
+               19, 11, 3, 60, 52, 44, 36,
+               63, 55, 47, 39, 31, 23, 15,
+               7, 62, 54, 46, 38, 30, 22,
+               14, 6, 61, 53, 45, 37, 29,
+               21, 13, 5, 28, 20, 12, 4
+       };
+
+       static int[] defPC2 = {
+               14, 17, 11, 24, 1, 5,
+               3, 28, 15, 6, 21, 10,
+               23, 19, 12, 4, 26, 8,
+               16, 7, 27, 20, 13, 2,
+               41, 52, 31, 37, 47, 55,
+               30, 40, 51, 45, 33, 48,
+               44, 49, 39, 56, 34, 53,
+               46, 42, 50, 36, 29, 32
+       };
+
+       static int[] rotK = {
+               1, 1, 2, 2, 2, 2, 2, 2,
+               1, 2, 2, 2, 2, 2, 2, 1
+       };
+
+       /*
+        * Permutations (and extractions) are implemented with the
+        * following tables, that are initialized from the class
+        * initialization code. The representation of a permutation is
+        * an array of words, where word at index k is a one-bit mask
+        * that identifies the source bit that should go at position k
+        * in the output. The "k" index here is in right-to-left
+        * convention: least significant bit (rightmost) is numbered 0.
+        *
+        * The Perm() method applies a permutation, using one of the
+        * tab*[] arrays.
+        *
+        * The S*[] arrays contain the S-boxes, with the permutation P
+        * effect merged in, and expecting their 6-bit input "as is".
+        */
+       static ulong[] tabPC1;
+       static ulong[] tabPC2;
+
+       static uint[] S1, S2, S3, S4, S5, S6, S7, S8;
+
+       static DES()
+       {
+               tabPC1 = new ulong[defPC1.Length];
+               for (int i = 0; i < defPC1.Length; i ++) {
+                       tabPC1[55 - i] = (ulong)1 << (64 - defPC1[i]);
+               }
+               tabPC2 = new ulong[defPC2.Length];
+               for (int i = 0; i < defPC2.Length; i ++) {
+                       tabPC2[47 - i] = (ulong)1 << (56 - defPC2[i]);
+               }
+
+               int[] PInv = new int[32];
+               for (int i = 0; i < defP.Length; i ++) {
+                       PInv[32 - defP[i]] = 31 - i;
+               }
+               S1 = MakeSbox(PInv, 0);
+               S2 = MakeSbox(PInv, 1);
+               S3 = MakeSbox(PInv, 2);
+               S4 = MakeSbox(PInv, 3);
+               S5 = MakeSbox(PInv, 4);
+               S6 = MakeSbox(PInv, 5);
+               S7 = MakeSbox(PInv, 6);
+               S8 = MakeSbox(PInv, 7);
+       }
+
+       static uint[] MakeSbox(int[] PInv, int i)
+       {
+               uint[] S = new uint[64];
+               for (int j = 0; j < 64; j ++) {
+                       int idx = ((j & 0x01) << 4) | (j & 0x20)
+                               | ((j & 0x1E) >> 1);
+                       uint zb = Sdef[i, idx];
+                       uint za = 0;
+                       for (int k = 0; k < 4; k ++) {
+                               za |= ((zb >> k) & 1)
+                                       << PInv[((7 - i) << 2) + k];
+                       }
+                       S[j] = za;
+               }
+               return S;
+       }
+
+       static ulong IPStep(ulong x, int size, ulong mask)
+       {
+               uint left = (uint)(x >> 32);
+               uint right = (uint)x;
+               uint tmp = ((left >> size) ^ right) & (uint)mask;
+               right ^= tmp;
+               left ^= tmp << size;
+               return ((ulong)left << 32) | (ulong)right;
+       }
+
+       static ulong DoIP(ulong x)
+       {
+               /*
+                * Permutation algorithm is initially from Richard
+                * Outerbridge; this implementation has been adapted
+                * from Crypto++ "des.cpp" file (which is in public
+                * domain).
+                */
+               uint l = (uint)(x >> 32);
+               uint r = (uint)x;
+               uint t;
+               t = ((l >>  4) ^ r) & 0x0F0F0F0F;
+               r ^= t;
+               l ^= t <<  4;
+               t = ((l >> 16) ^ r) & 0x0000FFFF;
+               r ^= t;
+               l ^= t << 16;
+               t = ((r >>  2) ^ l) & 0x33333333;
+               l ^= t;
+               r ^= t <<  2;
+               t = ((r >>  8) ^ l) & 0x00FF00FF;
+               l ^= t;
+               r ^= t <<  8;
+               t = ((l >>  1) ^ r) & 0x55555555;
+               r ^= t;
+               l ^= t <<  1;
+               x = ((ulong)l << 32) | (ulong)r;
+               return x;
+       }
+
+       static ulong DoIPInv(ulong x)
+       {
+               /*
+                * See DoIP().
+                */
+               uint l = (uint)(x >> 32);
+               uint r = (uint)x;
+               uint t;
+               t = ((l >>  1) ^ r) & 0x55555555;
+               r ^= t;
+               l ^= t <<  1;
+               t = ((r >>  8) ^ l) & 0x00FF00FF;
+               l ^= t;
+               r ^= t <<  8;
+               t = ((r >>  2) ^ l) & 0x33333333;
+               l ^= t;
+               r ^= t <<  2;
+               t = ((l >> 16) ^ r) & 0x0000FFFF;
+               r ^= t;
+               l ^= t << 16;
+               t = ((l >>  4) ^ r) & 0x0F0F0F0F;
+               r ^= t;
+               l ^= t <<  4;
+               x = ((ulong)l << 32) | (ulong)r;
+               return x;
+       }
+
+       /*
+        * Apply a permutation or extraction. For all k, bit k of the
+        * output (right-to-left numbering) is set if and only if the
+        * source bit in x defined by the tab[k] mask is set.
+        */
+       static ulong Perm(ulong[] tab, ulong x)
+       {
+               ulong y = 0;
+               for (int i = 0; i < tab.Length; i ++) {
+                       if ((x & tab[i]) != 0) {
+                               y |= (ulong)1 << i;
+                       }
+               }
+               return y;
+       }
+
+       static uint FConf(uint r0, ulong sk)
+       {
+               uint skhi = (uint)(sk >> 24);
+               uint sklo = (uint)sk;
+               uint r1 = (r0 >> 16) | (r0 << 16);
+               return
+                         S1[((r1 >> 11) ^ (skhi >> 18)) & 0x3F]
+                       | S2[((r0 >> 23) ^ (skhi >> 12)) & 0x3F]
+                       | S3[((r0 >> 19) ^ (skhi >>  6)) & 0x3F]
+                       | S4[((r0 >> 15) ^ (skhi      )) & 0x3F]
+                       | S5[((r0 >> 11) ^ (sklo >> 18)) & 0x3F]
+                       | S6[((r0 >>  7) ^ (sklo >> 12)) & 0x3F]
+                       | S7[((r0 >>  3) ^ (sklo >>  6)) & 0x3F]
+                       | S8[((r1 >> 15) ^ (sklo      )) & 0x3F];
+       }
+
+       /*
+        * 64-bit big-endian decoding.
+        */
+       static ulong Dec64be(byte[] buf, int off)
+       {
+               return ((ulong)buf[off] << 56)
+                       | ((ulong)buf[off + 1] << 48)
+                       | ((ulong)buf[off + 2] << 40)
+                       | ((ulong)buf[off + 3] << 32)
+                       | ((ulong)buf[off + 4] << 24)
+                       | ((ulong)buf[off + 5] << 16)
+                       | ((ulong)buf[off + 6] << 8)
+                       | (ulong)buf[off + 7];
+       }
+
+       /*
+        * 64-bit big-endian encoding.
+        */
+       static void Enc64be(ulong v, byte[] buf, int off)
+       {
+               buf[off + 0] = (byte)(v >> 56);
+               buf[off + 1] = (byte)(v >> 48);
+               buf[off + 2] = (byte)(v >> 40);
+               buf[off + 3] = (byte)(v >> 32);
+               buf[off + 4] = (byte)(v >> 24);
+               buf[off + 5] = (byte)(v >> 16);
+               buf[off + 6] = (byte)(v >> 8);
+               buf[off + 7] = (byte)v;
+       }
+}
+
+}
diff --git a/Crypto/DSAUtils.cs b/Crypto/DSAUtils.cs
new file mode 100644 (file)
index 0000000..c58cf90
--- /dev/null
@@ -0,0 +1,322 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class contains some utility methods used for DSA and ECDSA.
+ */
+
+public class DSAUtils {
+
+       /*
+        * Convert an ASN.1 DSA signature to "raw" format. A "raw" signature
+        * is the concatenation of two unsigned big-endian integers of
+        * the same length. An ASN.1 signature is a DER-encoded SEQUENCE
+        * of two INTEGER values. The returned signature will have the
+        * minimum length that can hold the two signature elements; use
+        * SigRawNormalize() to adjust that length.
+        *
+        * If the source signature is syntaxically invalid (not valid DER),
+        * then null is returned.
+        */
+       public static byte[] SigAsn1ToRaw(byte[] sig)
+       {
+               return SigAsn1ToRaw(sig, 0, sig.Length);
+       }
+
+       public static byte[] SigAsn1ToRaw(byte[] sig, int off, int len)
+       {
+               int lim = off + len;
+               if (len <= 2 || sig[off ++] != 0x30) {
+                       return null;
+               }
+               int tlen = DecodeLength(sig, ref off, lim);
+               if (tlen != (lim - off)) {
+                       return null;
+               }
+               int roff, rlen;
+               int soff, slen;
+               if (!DecodeInteger(sig, ref off, lim, out roff, out rlen)) {
+                       return null;
+               }
+               if (!DecodeInteger(sig, ref off, lim, out soff, out slen)) {
+                       return null;
+               }
+               if (off != lim) {
+                       return null;
+               }
+               int ulen = Math.Max(rlen, slen);
+               byte[] raw = new byte[ulen << 1];
+               Array.Copy(sig, roff, raw, ulen - rlen, rlen);
+               Array.Copy(sig, soff, raw, (ulen << 1) - slen, slen);
+               return raw;
+       }
+
+       static int DecodeLength(byte[] buf, ref int off, int lim)
+       {
+               if (off >= lim) {
+                       return -1;
+               }
+               int fb = buf[off ++];
+               if (fb < 0x80) {
+                       return fb;
+               }
+               int elen = fb - 0x80;
+               if (elen == 0) {
+                       return -1;
+               }
+               int acc = 0;
+               while (elen -- > 0) {
+                       if (off >= lim) {
+                               return -1;
+                       }
+                       if (acc > 0x7FFFFF) {
+                               return -1;
+                       }
+                       acc = (acc << 8) + buf[off ++];
+               }
+               return acc;
+       }
+
+       static bool DecodeInteger(byte[] buf, ref int off, int lim,
+               out int voff, out int vlen)
+       {
+               voff = -1;
+               vlen = -1;
+               if (off >= lim || buf[off ++] != 0x02) {
+                       return false;
+               }
+               int len = DecodeLength(buf, ref off, lim);
+               if (len <= 0 || len > (lim - off)) {
+                       return false;
+               }
+               voff = off;
+               vlen = len;
+               off += len;
+               while (vlen > 1 && buf[voff] == 0x00) {
+                       voff ++;
+                       vlen --;
+               }
+               return true;
+       }
+
+       /*
+        * Reduce a "raw" signature to its minimal length. The minimal
+        * length depends on the values of the inner elements; normally,
+        * that length is equal to twice the length of the encoded
+        * subgroup order, but it can be shorter by a few bytes
+        * (occasionally by two bytes; shorter signatures are very
+        * rare).
+        *
+        * If the source signature is null or has an odd length, then
+        * null is returned. If the source signature already has
+        * minimal length, then it is returned as is. Otherwise,
+        * a new array is created with the minimal length, filled,
+        * and returned.
+        */
+       public static byte[] SigRawMinimalize(byte[] sigRaw)
+       {
+               int minLen = GetMinRawLength(sigRaw);
+               if (minLen <= 0) {
+                       return null;
+               }
+               if (minLen == sigRaw.Length) {
+                       return sigRaw;
+               }
+               int m = sigRaw.Length >> 1;
+               int lh = minLen >> 1;
+               byte[] sig = new byte[lh + lh];
+               Array.Copy(sigRaw, m - lh, sig, 0, lh);
+               Array.Copy(sigRaw, m + m - lh, sig, lh, lh);
+               return sig;
+       }
+
+       /*
+        * Normalize a "raw" signature to the specified length. If
+        * the source array already has the right length, then it is
+        * returned as is. Otherwise, a new array is created with the
+        * requested length, and filled with the signature elements.
+        *
+        * If the source signature is null, or has an odd length, then
+        * null is returned. If the requested length is not valid (odd
+        * length) or cannot be achieved (because the signature elements
+        * are too large), then null is returned.
+        */
+       public static byte[] SigRawNormalize(byte[] sigRaw, int len)
+       {
+               int minLen = GetMinRawLength(sigRaw);
+               if (minLen <= 0) {
+                       return null;
+               }
+               if ((len & 1) != 0) {
+                       return null;
+               }
+               int hlen = len >> 1;
+               if (sigRaw.Length == len) {
+                       return sigRaw;
+               }
+               int m = sigRaw.Length >> 1;
+               int lh = minLen >> 1;
+               byte[] sig = new byte[len];
+               Array.Copy(sigRaw, m - lh, sig, hlen - lh, lh);
+               Array.Copy(sigRaw, m + m - lh, sig, len - lh, lh);
+               return sig;
+       }
+
+       static int GetMinRawLength(byte[] sig)
+       {
+               if (sig == null || (sig.Length & 1) != 0) {
+                       return -1;
+               }
+               int m = sig.Length << 1;
+               int lr, ls;
+               for (lr = m; lr > 0; lr --) {
+                       if (sig[m - lr] != 0) {
+                               break;
+                       }
+               }
+               for (ls = m; ls > 0; ls --) {
+                       if (sig[m + m - ls] != 0) {
+                               break;
+                       }
+               }
+               return Math.Max(lr, ls) << 1;
+       }
+
+       /*
+        * Convert a "raw" DSA signature to ASN.1. A "raw" signature
+        * is the concatenation of two unsigned big-endian integers of
+        * the same length. An ASN.1 signature is a DER-encoded SEQUENCE
+        * of two INTEGER values.
+        *
+        * If the source signature is syntaxically invalid (zero length,
+        * or odd length), then null is returned.
+        */
+       public static byte[] SigRawToAsn1(byte[] sig)
+       {
+               return SigRawToAsn1(sig, 0, sig.Length);
+       }
+
+       public static byte[] SigRawToAsn1(byte[] sig, int off, int len)
+       {
+               if (len <= 0 || (len & 1) != 0) {
+                       return null;
+               }
+               int tlen = len >> 1;
+               int rlen = LengthOfInteger(sig, off, tlen);
+               int slen = LengthOfInteger(sig, off + tlen, tlen);
+               int ulen = 1 + LengthOfLength(rlen) + rlen
+                       + 1 + LengthOfLength(slen) + slen;
+               byte[] s = new byte[1 + LengthOfLength(ulen) + ulen];
+               int k = 0;
+               s[k ++] = 0x30;
+               k += EncodeLength(ulen, s, k);
+               k += EncodeInteger(sig, off, tlen, s, k);
+               k += EncodeInteger(sig, off + tlen, tlen, s, k);
+               // DEBUG
+               if (k != s.Length) {
+                       throw new Exception("DSA internal error");
+               }
+               return s;
+       }
+
+       /*
+        * Get the length of the value of an INTEGER containing a
+        * specified value. Returned length includes the leading 0x00
+        * byte (if applicable) but not the tag or length fields.
+        */
+       static int LengthOfInteger(byte[] x, int off, int len)
+       {
+               while (len > 0 && x[off] == 0) {
+                       off ++;
+                       len --;
+               }
+               if (len == 0) {
+                       return 1;
+               }
+               return (x[off] >= 0x80) ? len + 1 : len;
+       }
+
+       static int LengthOfLength(int len)
+       {
+               if (len < 0x80) {
+                       return 1;
+               } else if (len < 0x100) {
+                       return 2;
+               } else if (len < 0x10000) {
+                       return 3;
+               } else if (len < 0x1000000) {
+                       return 4;
+               } else {
+                       return 5;
+               }
+       }
+
+       static int EncodeLength(int len, byte[] dst, int off)
+       {
+               if (len < 0x80) {
+                       dst[off] = (byte)len;
+                       return 1;
+               }
+               int k = 0;
+               for (int z = len; z != 0; z >>= 8) {
+                       k ++;
+               }
+               dst[off] = (byte)(0x80 + k);
+               for (int i = 0; i < k; i ++) {
+                       dst[off + k - i] = (byte)(len >> (i << 3));
+               }
+               return k + 1;
+       }
+
+       static int EncodeInteger(byte[] x, int off, int len,
+               byte[] dst, int dstOff)
+       {
+               int orig = dstOff;
+               dst[dstOff ++] = 0x02;
+               while (len > 0 && x[off] == 0) {
+                       off ++;
+                       len --;
+               }
+               if (len == 0) {
+                       dst[dstOff ++] = 0x01;
+                       dst[dstOff ++] = 0x00;
+                       return dstOff - orig;
+               }
+               if (x[off] >= 0x80) {
+                       dstOff += EncodeLength(len + 1, dst, dstOff);
+                       dst[dstOff ++] = 0x00;
+               } else {
+                       dstOff += EncodeLength(len, dst, dstOff);
+               }
+               Array.Copy(x, off, dst, dstOff, len);
+               dstOff += len;
+               return dstOff - orig;
+       }
+}
+
+}
diff --git a/Crypto/DigestCore.cs b/Crypto/DigestCore.cs
new file mode 100644 (file)
index 0000000..49171f0
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class is a convenient base class for implementations of IDigest.
+ * Hash function implementations must implement:
+ *    int DigestSize { get; }
+ *    int BlockSize { get; }
+ *    int PaddingOverhead { get; }
+ *    void Update(byte b)
+ *    void Update(byte[] buf, int off, int len)
+ *    void DoPartial(byte[] outBuf, int off)
+ *    void Reset()
+ *    IDigest Dup()
+ *
+ * Implementations SHOULD provide overrides for:
+ *    void CurrentState(byte[], int)
+ *
+ * In this class:
+ *    Update(byte[]) calls Update(byte[],int,int)
+ *    DoPartial() calls DoPartial(byte[],int)
+ *    DoFinal() calls DoPartial() and Reset()
+ *    DoFinal(byte[],int) calls DoPartial(byte[],int) and Reset()
+ */
+
+public abstract class DigestCore : IDigest {
+
+       /* see IDigest */
+       public abstract string Name { get; }
+
+       /* see IDigest */
+       public abstract int DigestSize { get; }
+
+       /* see IDigest */
+       public abstract int BlockSize { get; }
+
+       /* see IDigest */
+       public abstract void Update(byte b);
+
+       /* see IDigest */
+       public virtual void Update(byte[] buf)
+       {
+               Update(buf, 0, buf.Length);
+       }
+
+       /* see IDigest */
+       public abstract void Update(byte[] buf, int off, int len);
+
+       /* see IDigest */
+       public abstract void DoPartial(byte[] outBuf, int off);
+
+       /* see IDigest */
+       public virtual byte[] DoPartial()
+       {
+               byte[] buf = new byte[DigestSize];
+               DoPartial(buf, 0);
+               return buf;
+       }
+
+       /* see IDigest */
+       public virtual void DoFinal(byte[] outBuf, int off)
+       {
+               DoPartial(outBuf, off);
+               Reset();
+       }
+
+       /* see IDigest */
+       public virtual byte[] DoFinal()
+       {
+               byte[] r = DoPartial();
+               Reset();
+               return r;
+       }
+
+       /* see IDigest */
+       public abstract void Reset();
+
+       /* see IDigest */
+       public abstract IDigest Dup();
+
+       /*
+        * Default implementation throws a NotSupportedException.
+        */
+       public virtual void CurrentState(byte[] outBuf, int off)
+       {
+               throw new NotSupportedException();
+       }
+
+       /* see IDigest */
+       public virtual byte[] Hash(byte[] buf)
+       {
+               return Hash(buf, 0, buf.Length);
+       }
+
+       /* see IDigest */
+       public virtual byte[] Hash(byte[] buf, int off, int len)
+       {
+               IDigest h = Dup();
+               h.Reset();
+               h.Update(buf, off, len);
+               return h.DoFinal();
+       }
+}
+
+}
diff --git a/Crypto/EC.cs b/Crypto/EC.cs
new file mode 100644 (file)
index 0000000..959644d
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class contains static definitions for some standard elliptic
+ * curves.
+ */
+
+public class EC {
+
+       // public static ECCurve P192 = NIST.P192;
+       // public static ECCurve P224 = NIST.P224;
+       public static ECCurve P256 = NIST.P256;
+       public static ECCurve P384 = NIST.P384;
+       public static ECCurve P521 = NIST.P521;
+
+       public static ECCurve Curve25519 = new ECCurve25519();
+
+}
+
+}
diff --git a/Crypto/ECCurve.cs b/Crypto/ECCurve.cs
new file mode 100644 (file)
index 0000000..c92e7ec
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class represents an elliptic curve.
+ */
+
+public abstract class ECCurve {
+
+       /*
+        * Get the subgroup order for this curve (big-endian unsigned
+        * notation). The subgroup order is supposed to be a prime
+        * integer.
+        */
+       public byte[] SubgroupOrder {
+               get {
+                       return subgroupOrder;
+               }
+       }
+
+       /*
+        * Get the cofactor fors this curve (big-endian unsigned notation).
+        * The cofactor is the quotient of the curve order by the subgroup
+        * order.
+        */
+       public byte[] Cofactor {
+               get {
+                       return cofactor;
+               }
+       }
+
+       /*
+        * Get the curve symbolic name.
+        */
+       public abstract string Name {
+               get;
+       }
+
+       /*
+        * Get the curve type.
+        */
+       public abstract ECCurveType CurveType {
+               get;
+       }
+
+       /*
+        * Get the length (in bytes) of an encoded point. The "point at
+        * infinity" may use a shorter encoding than other points. This
+        * uses the "normal" encoding (not compressed).
+        */
+       public abstract int EncodedLength {
+               get;
+       }
+
+       /*
+        * Get the length (in bytes) of an encoded compressed point.
+        * The "point at infinity" may use a shorter encoding than
+        * other points.
+        */
+       public abstract int EncodedLengthCompressed {
+               get;
+       }
+
+       /*
+        * Perform extensive curve validity checks. These tests may be
+        * computationally expensive.
+        */
+       public abstract void CheckValid();
+
+       /*
+        * Get the encoded generator for this curve. This is
+        * a conventional point that generates the subgroup of prime
+        * order on which computations are normally done.
+        */
+       public virtual byte[] GetGenerator(bool compressed)
+       {
+               return MakeGenerator().Encode(compressed);
+       }
+
+       /*
+        * Get the offset and length of the X coordinate of a point,
+        * within its encoded representation. When doing a Diffie-Hellman
+        * key exchange, the resulting shared secret is the X coordinate
+        * of the resulting point.
+        */
+       public abstract int GetXoff(out int len);
+
+       /*
+        * Multiply the provided (encoded) point G by a scalar x. Scalar
+        * encoding is big-endian. The scalar value shall be non-zero and
+        * lower than the subgroup order (exception: some curves allow
+        * larger ranges).
+        *
+        * The result is written in the provided D[] array, using either
+        * compressed or uncompressed format (for some curves, output is
+        * always compressed). The array shall have the appropriate length.
+        * Returned value is -1 on success, 0 on error. If 0 is returned
+        * then the array contents are indeterminate.
+        *
+        * G and D need not be distinct arrays.
+        */
+       public uint Mul(byte[] G, byte[] x, byte[] D, bool compressed)
+       {
+               MutableECPoint P = MakeZero();
+               uint good = P.DecodeCT(G);
+               good &= ~P.IsInfinityCT;
+               good &= P.MulSpecCT(x);
+               good &= P.Encode(D, compressed);
+               return good;
+       }
+
+       /*
+        * Given points A and B, and scalar x and y, return x*A+y*B. This
+        * is used for ECDSA. Scalars use big-endian encoding and must be
+        * non-zero and lower than the subgroup order.
+        *
+        * The result is written in the provided D[] array, using either
+        * compressed or uncompressed format (for some curves, output is
+        * always compressed). The array shall have the appropriate length.
+        * Returned value is -1 on success, 0 on error. If 0 is returned
+        * then the array contents are indeterminate.
+        *
+        * Not all curves support this operation; if the curve does not,
+        * then an exception is thrown.
+        *
+        * A, B and D need not be distinct arrays.
+        */
+       public uint MulAdd(byte[] A, byte[] x, byte[] B, byte[] y,
+               byte[] D, bool compressed)
+       {
+               MutableECPoint P = MakeZero();
+               MutableECPoint Q = MakeZero();
+
+               /*
+                * Decode both points.
+                */
+               uint good = P.DecodeCT(A);
+               good &= Q.DecodeCT(B);
+               good &= ~P.IsInfinityCT & ~Q.IsInfinityCT;
+
+               /*
+                * Perform both point multiplications.
+                */
+               good &= P.MulSpecCT(x);
+               good &= Q.MulSpecCT(y);
+               good &= ~P.IsInfinityCT & ~Q.IsInfinityCT;
+
+               /*
+                * Perform addition. The AddCT() function may fail if
+                * P = Q, in which case we must compute 2Q and use that
+                * value instead.
+                */
+               uint z = P.AddCT(Q);
+               Q.DoubleCT();
+               P.Set(Q, ~z);
+
+               /*
+                * Encode the result. The Encode() function will report
+                * an error if the addition result is infinity.
+                */
+               good &= P.Encode(D, compressed);
+               return good;
+       }
+
+       /*
+        * Generate a new random secret value appropriate for an ECDH
+        * key exchange (WARNING: this might not be sufficiently uniform
+        * for the generation of the per-signature secret value 'k' for
+        * ECDSA).
+        *
+        * The value is returned in unsigned big-endian order, in an array
+        * of the same size of the subgroup order.
+        */
+       public abstract byte[] MakeRandomSecret();
+
+       /* ============================================================= */
+
+       byte[] subgroupOrder;
+       byte[] cofactor;
+
+       internal ECCurve(byte[] subgroupOrder, byte[] cofactor)
+       {
+               this.subgroupOrder = subgroupOrder;
+               this.cofactor = cofactor;
+       }
+
+       /*
+        * Create a new mutable point instance, initialized to the point
+        * at infinity.
+        *
+        * (On some curves whose implementations do not support generic
+        * point addition, this method may return a non-infinity point
+        * which serves as placeholder to obtain MutableECPoint instances.)
+        */
+       internal abstract MutableECPoint MakeZero();
+
+       /*
+        * Create a new mutable point instance, initialized to the
+        * defined subgroup generator.
+        */
+       internal abstract MutableECPoint MakeGenerator();
+
+       /*
+        * Create a new mutable point instance by decoding the provided
+        * value.
+        */
+       internal abstract MutableECPoint Decode(byte[] enc);
+}
+
+}
diff --git a/Crypto/ECCurve25519.cs b/Crypto/ECCurve25519.cs
new file mode 100644 (file)
index 0000000..c31b813
--- /dev/null
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Implementation for Curve25519.
+ */
+
+internal class ECCurve25519 : ECCurve {
+
+       public override string Name {
+               get {
+                       return "Curve25519";
+               }
+       }
+
+       public override ECCurveType CurveType {
+               get {
+                       return ECCurveType.Montgomery;
+               }
+       }
+
+       public override int EncodedLength {
+               get {
+                       return 32;
+               }
+       }
+
+       public override int EncodedLengthCompressed {
+               get {
+                       return 32;
+               }
+       }
+
+       public override byte[] GetGenerator(bool compressed)
+       {
+               byte[] G = new byte[32];
+               G[0] = 9;
+               return G;
+       }
+
+       private static byte[] ORDER = {
+               0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+               0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+               0x14, 0xDE, 0xF9, 0xDE, 0xA2, 0xF7, 0x9C, 0xD6,
+               0x58, 0x12, 0x63, 0x1A, 0x5C, 0xF5, 0xD3, 0xED
+       };
+
+       private static byte[] COFACTOR = {
+               0x08
+       };
+
+       private static byte[] MOD = {
+               0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+               0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+               0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+               0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xED
+       };
+
+       internal ModInt mp;
+       internal ModInt ma24;
+
+       internal ECCurve25519() : base(ORDER, COFACTOR)
+       {
+               mp = new ModInt(MOD);
+               ma24 = mp.Dup();
+               ma24.Set(121665);
+               ma24.ToMonty();
+       }
+
+       public override void CheckValid()
+       {
+               /* Nothing to do, the curve is valid by construction. */
+       }
+
+       public override int GetXoff(out int len)
+       {
+               len = 32;
+               return 0;
+       }
+
+       /* obsolete
+       public override uint Mul(byte[] G, byte[] x, byte[] D, bool compressed)
+       {
+               if (G.Length != 32 || D.Length != 32 || x.Length > 32) {
+                       return 0;
+               }
+
+               byte[] k = new byte[32];
+               Array.Copy(x, 0, k, 32 - x.Length, x.Length);
+
+               k[31] &= 0xF8;
+               k[0] &= 0x7F;
+               k[0] |= 0x40;
+
+               byte[] u = new byte[32];
+               for (int i = 0; i < 32; i ++) {
+                       u[i] = G[31 - i];
+               }
+               u[0] &= 0x7F;
+
+               ModInt x1 = mp.Dup();
+               x1.DecodeReduce(u);
+               x1.ToMonty();
+               ModInt x2 = mp.Dup();
+               x2.SetMonty(0xFFFFFFFF);
+               ModInt z2 = mp.Dup();
+               ModInt x3 = x1.Dup();
+               ModInt z3 = x2.Dup();
+               uint swap = 0;
+
+               ModInt a = mp.Dup();
+               ModInt aa = mp.Dup();
+               ModInt b = mp.Dup();
+               ModInt bb = mp.Dup();
+               ModInt c = mp.Dup();
+               ModInt d = mp.Dup();
+               ModInt e = mp.Dup();
+
+               for (int t = 254; t >= 0; t --) {
+                       uint kt = (uint)-((k[31 - (t >> 3)] >> (t & 7)) & 1);
+                       swap ^= kt;
+                       x2.CondSwap(x3, swap);
+                       z2.CondSwap(z3, swap);
+                       swap = kt;
+
+                       a.Set(x2);
+                       a.Add(z2);
+                       aa.Set(a);
+                       aa.MontySquare();
+                       b.Set(x2);
+                       b.Sub(z2);
+                       bb.Set(b);
+                       bb.MontySquare();
+                       e.Set(aa);
+                       e.Sub(bb);
+                       c.Set(x3);
+                       c.Add(z3);
+                       d.Set(x3);
+                       d.Sub(z3);
+                       d.MontyMul(a);
+                       c.MontyMul(b);
+                       x3.Set(d);
+                       x3.Add(c);
+                       x3.MontySquare();
+                       z3.Set(d);
+                       z3.Sub(c);
+                       z3.MontySquare();
+                       z3.MontyMul(x1);
+                       x2.Set(aa);
+                       x2.MontyMul(bb);
+                       z2.Set(e);
+                       z2.MontyMul(ma24);
+                       z2.Add(aa);
+                       z2.MontyMul(e);
+               }
+               x2.CondSwap(x3, swap);
+               z2.CondSwap(z3, swap);
+
+               z2.FromMonty();
+               z2.Invert();
+               x2.MontyMul(z2);
+
+               x2.Encode(u);
+               for (int i = 0; i < 32; i ++) {
+                       D[i] = u[31 - i];
+               }
+               return 0xFFFFFFFF;
+       }
+
+       public override uint MulAdd(byte[] A, byte[] x, byte[] B, byte[] y,
+               byte[] D, bool compressed)
+       {
+               throw new CryptoException(
+                       "Operation not supported for Curve25519");
+       }
+       */
+
+       public override bool Equals(object obj)
+       {
+               return (obj as ECCurve25519) != null;
+       }
+       
+       public override int GetHashCode()
+       {
+               return 0x3E96D5F6;
+       }
+
+       public override byte[] MakeRandomSecret()
+       {
+               /*
+                * For Curve25519, we simply generate a random 32-byte
+                * array, to which we apply the "clamping" that will
+                * be done for point multiplication anyway.
+                */
+               byte[] x = new byte[32];
+               RNG.GetBytes(x);
+               x[0] &= 0x7F;
+               x[0] |= 0x40;
+               x[31] &= 0xF8;
+               return x;
+       }
+
+       internal override MutableECPoint MakeZero()
+       {
+               return new MutableECPointCurve25519();
+       }
+
+       internal override MutableECPoint MakeGenerator()
+       {
+               MutableECPointCurve25519 G = new MutableECPointCurve25519();
+               G.Decode(GetGenerator(false));
+               return G;
+       }
+
+       internal override MutableECPoint Decode(byte[] enc)
+       {
+               MutableECPointCurve25519 P = new MutableECPointCurve25519();
+               P.Decode(enc);
+               return P;
+       }
+}
+
+}
diff --git a/Crypto/ECCurvePrime.cs b/Crypto/ECCurvePrime.cs
new file mode 100644 (file)
index 0000000..f2621bb
--- /dev/null
@@ -0,0 +1,337 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Implementation for elliptic curves in a prime field.
+ */
+
+internal class ECCurvePrime : ECCurve {
+
+       public override string Name {
+               get {
+                       return name;
+               }
+       }
+
+       public override ECCurveType CurveType {
+               get {
+                       return ECCurveType.Prime;
+               }
+       }
+
+       public override int EncodedLength {
+               get {
+                       return 1 + (flen << 1);
+               }
+       }
+
+       public override int EncodedLengthCompressed {
+               get {
+                       return 1 + flen;
+               }
+       }
+
+       string name;
+
+       /*
+        * 'mp' always contains 0 modulo p.
+        * 'ma' contains a.
+        * 'mb' contains b.
+        * pMod4 is p modulo 4.
+        */
+       internal ModInt mp;
+       internal ModInt ma;
+       internal ModInt mb;
+       internal int pMod4;
+
+       /*
+        * a and b are in unsigned big-endian notation.
+        * aIsM3 is true when a == -3 modulo p.
+        */
+       internal byte[] a;
+       internal byte[] b;
+       internal bool aIsM3;
+
+       internal byte[] mod;
+       internal int flen;
+       byte[] gx;
+       byte[] gy;
+       int hashCode;
+
+       /*
+        * Checks enforced by the constructor:
+        * -- modulus is odd and at least 80-bit long
+        * -- subgroup order is odd and at least 30-bit long
+        * -- parameters a[] and b[] are lower than modulus
+        * -- coordinates gx and gy are lower than modulus
+        * -- coordinates gx and gy match curve equation
+        */
+       internal ECCurvePrime(string name, byte[] mod, byte[] a, byte[] b,
+               byte[] gx, byte[] gy, byte[] subgroupOrder, byte[] cofactor)
+               : base(subgroupOrder, cofactor)
+       {
+               this.mod = mod = BigInt.NormalizeBE(mod);
+               int modLen = BigInt.BitLength(mod);
+               if (modLen < 80) {
+                       throw new CryptoException(
+                               "Invalid curve: modulus is too small");
+               }
+               if ((mod[mod.Length - 1] & 0x01) == 0) {
+                       throw new CryptoException(
+                               "Invalid curve: modulus is even");
+               }
+               int sgLen = BigInt.BitLength(subgroupOrder);
+               if (sgLen < 30) {
+                       throw new CryptoException(
+                               "Invalid curve: subgroup is too small");
+               }
+               if ((subgroupOrder[subgroupOrder.Length - 1] & 0x01) == 0) {
+                       throw new CryptoException(
+                               "Invalid curve: subgroup order is even");
+               }
+
+               mp = new ModInt(mod);
+               flen = (modLen + 7) >> 3;
+               pMod4 = mod[mod.Length - 1] & 3;
+
+               this.a = a = BigInt.NormalizeBE(a);
+               this.b = b = BigInt.NormalizeBE(b);
+               if (BigInt.CompareCT(a, mod) >= 0
+                       || BigInt.CompareCT(b, mod) >= 0)
+               {
+                       throw new CryptoException(
+                               "Invalid curve: out-of-range parameter");
+               }
+               ma = mp.Dup();
+               ma.Decode(a);
+               ma.Add(3);
+               aIsM3 = ma.IsZero;
+               ma.Sub(3);
+               mb = mp.Dup();
+               mb.Decode(b);
+
+               this.gx = gx = BigInt.NormalizeBE(gx);
+               this.gy = gy = BigInt.NormalizeBE(gy);
+               if (BigInt.CompareCT(gx, mod) >= 0
+                       || BigInt.CompareCT(gy, mod) >= 0)
+               {
+                       throw new CryptoException(
+                               "Invalid curve: out-of-range coordinates");
+               }
+               MutableECPointPrime G = new MutableECPointPrime(this);
+               G.Set(gx, gy, true);
+
+               hashCode = (int)(BigInt.HashInt(mod)
+                       ^ BigInt.HashInt(a) ^ BigInt.HashInt(b)
+                       ^ BigInt.HashInt(gx) ^ BigInt.HashInt(gy));
+
+               if (name == null) {
+                       name = string.Format("generic prime {0}/{1}",
+                               modLen, sgLen);
+               }
+               this.name = name;
+       }
+
+       /*
+        * Extra checks:
+        * -- modulus is prime
+        * -- subgroup order is prime
+        * -- generator indeed generates subgroup
+        */
+       public override void CheckValid()
+       {
+               /*
+                * Check that the modulus is prime.
+                */
+               if (!BigInt.IsPrime(mod)) {
+                       throw new CryptoException(
+                               "Invalid curve: modulus is not prime");
+               }
+
+               /*
+                * Check that the subgroup order is prime.
+                */
+               if (!BigInt.IsPrime(SubgroupOrder)) {
+                       throw new CryptoException(
+                               "Invalid curve: subgroup order is not prime");
+               }
+
+               /*
+                * Check that the G point is indeed a generator of the
+                * subgroup. Note that since it has explicit coordinates,
+                * it cannot be the point at infinity; it suffices to
+                * verify that, when multiplied by the subgroup order,
+                * it yields infinity.
+                */
+               MutableECPointPrime G = new MutableECPointPrime(this);
+               G.Set(gx, gy, false);
+               if (G.MulSpecCT(SubgroupOrder) == 0 || !G.IsInfinity) {
+                       throw new CryptoException(
+                               "Invalid curve: generator does not match"
+                               + " subgroup order");
+               }
+
+               /*
+                * TODO: check cofactor.
+                *
+                * If the cofactor is small, then we can simply compute
+                * the complete curve order by multiplying the cofactor
+                * with the subgroup order, and see whether it is in the
+                * proper range with regards to the field cardinal (by
+                * using Hasse's theorem). However, if the cofactor is
+                * larger than the subgroup order, then detecting a
+                * wrong cofactor value is a bit more complex. We could
+                * generate a few random points and multiply them by
+                * the computed order, but this may be expensive.
+                */
+       }
+
+       public override int GetXoff(out int len)
+       {
+               len = flen;
+               return 1;
+       }
+
+       /* obsolete
+       public override uint Mul(byte[] G, byte[] x, byte[] D, bool compressed)
+       {
+               MutableECPointPrime P = new MutableECPointPrime(this);
+               uint good = P.DecodeCT(G);
+               good &= ~P.IsInfinityCT;
+               good &= P.MulSpecCT(x);
+               good &= P.Encode(D, compressed);
+               return good;
+       }
+
+       public override uint MulAdd(byte[] A, byte[] x, byte[] B, byte[] y,
+               byte[] D, bool compressed)
+       {
+               MutableECPointPrime P = new MutableECPointPrime(this);
+               MutableECPointPrime Q = new MutableECPointPrime(this);
+
+               uint good = P.DecodeCT(A);
+               good &= Q.DecodeCT(B);
+               good &= ~P.IsInfinityCT & ~Q.IsInfinityCT;
+
+               good &= P.MulSpecCT(x);
+               good &= Q.MulSpecCT(y);
+               good &= ~P.IsInfinityCT & ~Q.IsInfinityCT;
+
+               uint z = P.AddCT(Q);
+               Q.DoubleCT();
+               P.Set(Q, ~z);
+
+               good &= P.Encode(D, compressed);
+               return good;
+       }
+       */
+
+       public override byte[] MakeRandomSecret()
+       {
+               /*
+                * We force the top bits to 0 to guarantee that the value
+                * is less than the subgroup order; and we force the
+                * least significant bit to 0 so that the value is not null.
+                * This is good enough for ECDH.
+                */
+               byte[] q = SubgroupOrder;
+               byte[] x = new byte[q.Length];
+               int mask = 0xFF;
+               while (mask >= q[0]) {
+                       mask >>= 1;
+               }
+               RNG.GetBytes(x);
+               x[0] &= (byte)mask;
+               x[x.Length - 1] |= (byte)0x01;
+               return x;
+       }
+
+       internal override MutableECPoint MakeZero()
+       {
+               return new MutableECPointPrime(this);
+       }
+
+       internal override MutableECPoint MakeGenerator()
+       {
+               /*
+                * We do not have to check the generator, since
+                * it was already done in the constructor.
+                */
+               MutableECPointPrime G = new MutableECPointPrime(this);
+               G.Set(gx, gy, false);
+               return G;
+       }
+
+       internal override MutableECPoint Decode(byte[] enc)
+       {
+               MutableECPointPrime P = new MutableECPointPrime(this);
+               P.Decode(enc);
+               return P;
+       }
+       
+       public override bool Equals(object obj)
+       {
+               return Equals(obj as ECCurvePrime);
+       }
+       
+       internal bool Equals(ECCurvePrime curve)
+       {
+               if (this == curve) {
+                       return true;
+               }
+               return BigInt.Compare(mod, curve.mod) == 0
+                       && BigInt.Compare(a, curve.a) == 0
+                       && BigInt.Compare(b, curve.b) == 0
+                       && BigInt.Compare(gx, curve.gx) == 0
+                       && BigInt.Compare(gy, curve.gy) == 0;
+       }
+
+       public override int GetHashCode()
+       {
+               return hashCode;
+       }
+
+       /*
+        * Given a value X in sx, this method computes X^3+aX+b into sd.
+        * 'sx' is unmodified. 'st' is modified (it receives a*X).
+        * The sx, sd and st instances MUST be distinct.
+        */
+       internal void RebuildY2(ModInt sx, ModInt sd, ModInt st)
+       {
+               sd.Set(sx);
+               sd.ToMonty();
+               st.Set(sd);
+               sd.MontySquare();
+               sd.MontyMul(sx);
+               st.MontyMul(ma);
+               sd.Add(st);
+               sd.Add(mb);
+       }
+}
+
+}
diff --git a/Crypto/ECCurveType.cs b/Crypto/ECCurveType.cs
new file mode 100644 (file)
index 0000000..c875c0a
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Symbolic identifiers for curve types.
+ */
+
+public enum ECCurveType {
+
+       Prime,
+       BinaryTrinomial,
+       BinaryPentanomial,
+       Montgomery,
+       Edwards
+}
+
+}
diff --git a/Crypto/ECDSA.cs b/Crypto/ECDSA.cs
new file mode 100644 (file)
index 0000000..e10e6d6
--- /dev/null
@@ -0,0 +1,400 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.IO;
+
+namespace Crypto {
+
+/*
+ * This class implements the ECDSA signature algorithm. The static methods
+ * provide access to the algorithm primitives. The hash of the signed data
+ * must be provided externally.
+ *
+ * Signatures may be encoded in either "ASN.1" or "raw" formats; "ASN.1"
+ * is normally used (e.g. in SSL/TLS) but some protocols and API expect
+ * the raw format (e.g. PKCS#11 and OpenPGP). An ECDSA signature consists
+ * in two integers r and s, which are values modulo the subgroup order q.
+ * In ASN.1 format, the signature is the DER encoding of the ASN.1
+ * structure:
+ *
+ *    ECDSA-signature ::= SEQUENCE {
+ *            r INTEGER,
+ *            s INTEGER
+ *    }
+ *
+ * In raw format, the two integers r and s are encoded with unsigned
+ * big-endian encoding (with the same encoding length) and concatenated.
+ *
+ * The Sign() and Verify() methods use the ASN.1 format. The SignRaw()
+ * and VerifyRaw() use the raw format. The SigRawToAsn1() and SigAsn1ToRaw()
+ * allow for converting between the two formats.
+ */
+
+public class ECDSA : DSAUtils {
+
+       /*
+        * Verify an ECDSA signature (ASN.1 format). Returned value is true
+        * on success. If the signature is invalid, then false is returned.
+        * Internal exceptions (due to an incorrect public key) are also
+        * converted to a returned value of false.
+        *
+        * There are four methods, depending on the source operands.
+        */
+
+       public static bool Verify(ECPublicKey pk,
+               byte[] hash, byte[] sig)
+       {
+               return Verify(pk, hash, 0, hash.Length, sig, 0, sig.Length);
+       }
+
+       public static bool Verify(ECPublicKey pk,
+               byte[] hash, int hashOff, int hashLen, byte[] sig)
+       {
+               return Verify(pk, hash, hashOff, hashLen, sig, 0, sig.Length);
+       }
+
+       public static bool Verify(ECPublicKey pk,
+               byte[] hash, byte[] sig, int sigOff, int sigLen)
+       {
+               return Verify(pk, hash, 0, hash.Length, sig, sigOff, sigLen);
+       }
+
+       public static bool Verify(ECPublicKey pk,
+               byte[] hash, int hashOff, int hashLen,
+               byte[] sig, int sigOff, int sigLen)
+       {
+               byte[] raw = SigAsn1ToRaw(sig, sigOff, sigLen);
+               if (raw == null) {
+                       return false;
+               }
+               return VerifyRaw(pk,
+                       hash, hashOff, hashLen, raw, 0, raw.Length);
+       }
+
+       /*
+        * Verify an ECDSA signature (raw format). Returned value is true
+        * on success. If the signature is invalid, then false is returned.
+        * Internal exceptions (due to an incorrect public key) are also
+        * converted to a returned value of false.
+        *
+        * There are four methods, depending on the source operands.
+        */
+
+       public static bool VerifyRaw(ECPublicKey pk,
+               byte[] hash, byte[] sig)
+       {
+               return VerifyRaw(pk,
+                       hash, 0, hash.Length, sig, 0, sig.Length);
+       }
+
+       public static bool VerifyRaw(ECPublicKey pk,
+               byte[] hash, int hashOff, int hashLen, byte[] sig)
+       {
+               return VerifyRaw(pk,
+                       hash, hashOff, hashLen, sig, 0, sig.Length);
+       }
+
+       public static bool VerifyRaw(ECPublicKey pk,
+               byte[] hash, byte[] sig, int sigOff, int sigLen)
+       {
+               return VerifyRaw(pk,
+                       hash, 0, hash.Length, sig, sigOff, sigLen);
+       }
+
+       public static bool VerifyRaw(ECPublicKey pk,
+               byte[] hash, int hashOff, int hashLen,
+               byte[] sig, int sigOff, int sigLen)
+       {
+               try {
+                       /*
+                        * Get the curve.
+                        */
+                       ECCurve curve = pk.Curve;
+
+                       /*
+                        * Get r and s from signature. This also verifies
+                        * that they do not exceed the subgroup order.
+                        */
+                       if (sigLen == 0 || (sigLen & 1) != 0) {
+                               return false;
+                       }
+                       int tlen = sigLen >> 1;
+                       ModInt oneQ = new ModInt(curve.SubgroupOrder);
+                       oneQ.Set(1);
+                       ModInt r = oneQ.Dup();
+                       ModInt s = oneQ.Dup();
+                       r.Decode(sig, sigOff, tlen);
+                       s.Decode(sig, sigOff + tlen, tlen);
+
+                       /*
+                        * If either r or s was too large, it got set to
+                        * zero. We also don't want real zeros.
+                        */
+                       if (r.IsZero || s.IsZero) {
+                               return false;
+                       }
+
+                       /*
+                        * Convert the hash value to an integer modulo q.
+                        * As per FIPS 186-4, if the hash value is larger
+                        * than q, then we keep the qlen leftmost bits of
+                        * the hash value.
+                        */
+                       int qBitLength = oneQ.ModBitLength;
+                       int hBitLength = hashLen << 3;
+                       byte[] hv;
+                       if (hBitLength <= qBitLength) {
+                               hv = new byte[hashLen];
+                               Array.Copy(hash, hashOff, hv, 0, hashLen);
+                       } else {
+                               int qlen = (qBitLength + 7) >> 3;
+                               hv = new byte[qlen];
+                               Array.Copy(hash, hashOff, hv, 0, qlen);
+                               int rs = (8 - (qBitLength & 7)) & 7;
+                               BigInt.RShift(hv, rs);
+                       }
+                       ModInt z = oneQ.Dup();
+                       z.DecodeReduce(hv);
+
+                       /*
+                        * Apply the verification algorithm:
+                        *   w = 1/s mod q
+                        *   u = z*w mod q
+                        *   v = r*w mod q
+                        *   T = u*G + v*Pub
+                        *   test whether T.x mod q == r.
+                        */
+                       /*
+                        * w = 1/s mod q
+                        */
+                       ModInt w = s.Dup();
+                       w.Invert();
+
+                       /*
+                        * u = z*w mod q
+                        */
+                       w.ToMonty();
+                       ModInt u = w.Dup();
+                       u.MontyMul(z);
+
+                       /*
+                        * v = r*w mod q
+                        */
+                       ModInt v = w.Dup();
+                       v.MontyMul(r);
+
+                       /*
+                        * Compute u*G
+                        */
+                       MutableECPoint T = curve.MakeGenerator();
+                       uint good = T.MulSpecCT(u.Encode());
+
+                       /*
+                        * Compute v*iPub
+                        */
+                       MutableECPoint M = pk.iPub.Dup();
+                       good &= M.MulSpecCT(v.Encode());
+
+                       /*
+                        * Compute T = u*G+v*iPub
+                        */
+                       uint nd = T.AddCT(M);
+                       M.DoubleCT();
+                       T.Set(M, ~nd);
+                       good &= ~T.IsInfinityCT;
+
+                       /*
+                        * Get T.x, reduced modulo q.
+                        * Signature is valid if and only if we get
+                        * the same value as r (and we did not encounter
+                        * an error previously).
+                        */
+                       s.DecodeReduce(T.X);
+                       return (good & r.EqCT(s)) != 0;
+
+               } catch (CryptoException) {
+                       /*
+                        * Exceptions may occur if the key or signature
+                        * have invalid values (non invertible, out of
+                        * range...). Any such occurrence means that the
+                        * signature is not valid.
+                        */
+                       return false;
+               }
+       }
+
+       /*
+        * Compute an ECDSA signature (ASN.1 format). On error (e.g. due
+        * to an invalid private key), an exception is thrown.
+        *
+        * Internally, the process described in RFC 6979 is used to
+        * compute the per-signature random value 'k'. If 'rfc6979Hash'
+        * is not null, then a clone of that function is used for that
+        * process, and signatures are fully deterministic and should
+        * match RFC 6979 test vectors; if 'rfc6979Hash' is null, then
+        * the engine uses SHA-256 with additional randomness, resulting
+        * in randomized signatures. The systematic use of RFC 6979 in
+        * both cases ensures the safety of the private key even if the
+        * system RNG is predictible.
+        *
+        * There are four methods, depending on the source operands.
+        */
+
+       public static byte[] Sign(ECPrivateKey sk, IDigest rfc6979Hash,
+               byte[] hash)
+       {
+               return Sign(sk, rfc6979Hash, hash, 0, hash.Length);
+       }
+
+       public static byte[] Sign(ECPrivateKey sk, IDigest rfc6979Hash,
+               byte[] hash, int hashOff, int hashLen)
+       {
+               return SigRawToAsn1(SignRaw(sk, rfc6979Hash,
+                       hash, hashOff, hashLen));
+       }
+
+       public static int Sign(ECPrivateKey sk, IDigest rfc6979Hash,
+               byte[] hash, byte[] outBuf, int outOff)
+       {
+               return Sign(sk, rfc6979Hash,
+                       hash, 0, hash.Length, outBuf, outOff);
+       }
+
+       public static int Sign(ECPrivateKey sk, IDigest rfc6979Hash,
+               byte[] hash, int hashOff, int hashLen,
+               byte[] outBuf, int outOff)
+       {
+               byte[] sig = Sign(sk, rfc6979Hash, hash, hashOff, hashLen);
+               Array.Copy(sig, 0, outBuf, outOff, sig.Length);
+               return sig.Length;
+       }
+
+       /*
+        * Compute an ECDSA signature (raw format). On error (e.g. due
+        * to an invalid private key), an exception is thrown.
+        *
+        * Internally, the process described in RFC 6979 is used to
+        * compute the per-signature random value 'k'. If 'rfc6979Hash'
+        * is not null, then a clone of that function is used for that
+        * process, and signatures are fully deterministic and should
+        * match RFC 6979 test vectors; if 'rfc6979Hash' is null, then
+        * the engine uses SHA-256 with additional randomness, resulting
+        * in randomized signatures. The systematic use of RFC 6979 in
+        * both cases ensures the safety of the private key even if the
+        * system RNG is predictible.
+        *
+        * The signature returned by these methods always has length
+        * exactly twice that of the encoded subgroup order (they are
+        * not minimalized). Use SigRawMinimalize() to reduce the
+        * signature size to its minimum length.
+        *
+        * There are four methods, depending on the source operands.
+        */
+
+       public static byte[] SignRaw(ECPrivateKey sk, IDigest rfc6979Hash,
+               byte[] hash)
+       {
+               return SignRaw(sk, rfc6979Hash, hash, 0, hash.Length);
+       }
+
+       public static int SignRaw(ECPrivateKey sk, IDigest rfc6979Hash,
+               byte[] hash, byte[] outBuf, int outOff)
+       {
+               return SignRaw(sk, rfc6979Hash,
+                       hash, 0, hash.Length, outBuf, outOff);
+       }
+
+       public static int SignRaw(ECPrivateKey sk, IDigest rfc6979Hash,
+               byte[] hash, int hashOff, int hashLen,
+               byte[] outBuf, int outOff)
+       {
+               byte[] sig = SignRaw(sk, rfc6979Hash, hash, hashOff, hashLen);
+               Array.Copy(sig, 0, outBuf, outOff, sig.Length);
+               return sig.Length;
+       }
+
+       public static byte[] SignRaw(ECPrivateKey sk, IDigest rfc6979Hash,
+               byte[] hash, int hashOff, int hashLen)
+       {
+               ECCurve curve = sk.Curve;
+               byte[] q = curve.SubgroupOrder;
+               RFC6979 rf = new RFC6979(rfc6979Hash, q, sk.X,
+                       hash, hashOff, hashLen, rfc6979Hash != null);
+               ModInt mh = rf.GetHashMod();
+               ModInt mx = mh.Dup();
+               mx.Decode(sk.X);
+
+               /*
+                * Compute DSA signature. We use a loop to enumerate
+                * candidates for k until a proper one is found (it
+                * is VERY improbable that we may have to loop).
+                */
+               ModInt mr = mh.Dup();
+               ModInt ms = mh.Dup();
+               ModInt mk = mh.Dup();
+               byte[] k = new byte[q.Length];
+               for (;;) {
+                       rf.NextK(k);
+                       MutableECPoint G = curve.MakeGenerator();
+                       if (G.MulSpecCT(k) == 0) {
+                               /*
+                                * We may get an error here only if the
+                                * curve is invalid (generator does not
+                                * produce the expected subgroup).
+                                */
+                               throw new CryptoException(
+                                       "Invalid EC private key / curve");
+                       }
+                       mr.DecodeReduce(G.X);
+                       if (mr.IsZero) {
+                               continue;
+                       }
+                       ms.Set(mx);
+                       ms.ToMonty();
+                       ms.MontyMul(mr);
+                       ms.Add(mh);
+                       mk.Decode(k);
+                       mk.Invert();
+                       ms.ToMonty();
+                       ms.MontyMul(mk);
+
+                       byte[] sig = new byte[q.Length << 1];
+                       mr.Encode(sig, 0, q.Length);
+                       ms.Encode(sig, q.Length, q.Length);
+                       return sig;
+               }
+       }
+
+       /*
+        * Generate a new EC key pair in the specified curve.
+        */
+       public static ECPrivateKey Generate(ECCurve curve)
+       {
+               return new ECPrivateKey(curve,
+                       BigInt.RandIntNZ(curve.SubgroupOrder));
+       }
+}
+
+}
diff --git a/Crypto/ECPrivateKey.cs b/Crypto/ECPrivateKey.cs
new file mode 100644 (file)
index 0000000..d63692d
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class contains an EC private key, consisting of two elements:
+ * -- an elliptic curve
+ * -- the private integer (X)
+ *
+ * The private integer is always handled as an integer modulo the
+ * curve subbroup order. Its binary representation is unsigned big-endian
+ * with exactly the same length as the subgroup order.
+ */
+
+public class ECPrivateKey : IPrivateKey {
+
+       public ECCurve Curve {
+               get {
+                       return curve;
+               }
+       }
+
+       public byte[] X {
+               get {
+                       return priv;
+               }
+       }
+
+       public int KeySizeBits {
+               get {
+                       return BigInt.BitLength(curve.SubgroupOrder);
+               }
+       }
+
+       public string AlgorithmName {
+               get {
+                       return "EC";
+               }
+       }
+
+       IPublicKey IPrivateKey.PublicKey {
+               get {
+                       return this.PublicKey;
+               }
+       }
+
+       public ECPublicKey PublicKey {
+               get {
+                       if (dpk == null) {
+                               MutableECPoint G = curve.MakeGenerator();
+                               G.MulSpecCT(priv);
+                               dpk = new ECPublicKey(curve, G.Encode(false));
+                       }
+                       return dpk;
+               }
+       }
+
+       ECCurve curve;
+       byte[] priv;
+       ECPublicKey dpk;
+
+       /*
+        * Create a new instance with the provided elements. The
+        * constructor verifies that the provided private integer
+        * is non-zero and is less than the subgroup order.
+        */
+       public ECPrivateKey(ECCurve curve, byte[] X)
+       {
+               this.curve = curve;
+               ModInt ms = new ModInt(curve.SubgroupOrder);
+               uint good = ms.Decode(X);
+               good &= ~ms.IsZeroCT;
+               if (good == 0) {
+                       throw new CryptoException("Invalid private key");
+               }
+               priv = ms.Encode();
+               dpk = null;
+       }
+
+       /*
+        * CheckValid() runs the validity tests on the curve.
+        */
+       public void CheckValid()
+       {
+               curve.CheckValid();
+       }
+}
+
+}
diff --git a/Crypto/ECPublicKey.cs b/Crypto/ECPublicKey.cs
new file mode 100644 (file)
index 0000000..9db1c86
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class contains an EC public key, consisting of two elements:
+ * -- an elliptic curve
+ * -- a non-zero point on that curve (called "Pub")
+ */
+
+public class ECPublicKey : IPublicKey {
+
+       public ECCurve Curve {
+               get {
+                       return curve;
+               }
+       }
+
+       public byte[] Pub {
+               get {
+                       return pub;
+               }
+       }
+
+       public int KeySizeBits {
+               get {
+                       return BigInt.BitLength(curve.SubgroupOrder);
+               }
+       }
+
+       public string AlgorithmName {
+               get {
+                       return "EC";
+               }
+       }
+
+       ECCurve curve;
+       byte[] pub;
+       int hashCode;
+
+       internal MutableECPoint iPub;
+
+       /*
+        * Create a new instance with the provided elements. The
+        * constructor verifies that the provided point is part of
+        * the curve.
+        */
+       public ECPublicKey(ECCurve curve, byte[] Pub)
+       {
+               this.curve = curve;
+               this.pub = Pub;
+               iPub = curve.Decode(Pub);
+               if (iPub.IsInfinity) {
+                       throw new CryptoException(
+                               "Public key point is infinity");
+               }
+               hashCode = curve.GetHashCode()
+                       ^ (int)BigInt.HashInt(iPub.X)
+                       ^ (int)BigInt.HashInt(iPub.Y);
+       }
+
+       /*
+        * CheckValid() runs the validity tests on the curve, and
+        * verifies that provided point is part of a subgroup with
+        * the advertised subgroup order.
+        */
+       public void CheckValid()
+       {
+               curve.CheckValid();
+               MutableECPoint P = iPub.Dup();
+               if (P.MulSpecCT(curve.SubgroupOrder) == 0
+                       || !P.IsInfinity)
+               {
+                       throw new CryptoException(
+                               "Public key point not on the defined subgroup");
+               }
+       }
+
+       public override bool Equals(object obj)
+       {
+               ECPublicKey pk = obj as ECPublicKey;
+               if (pk == null) {
+                       return false;
+               }
+               if (hashCode != pk.hashCode) {
+                       return false;
+               }
+               return iPub.Eq(pk.iPub);
+       }
+
+       public override int GetHashCode()
+       {
+               return hashCode;
+       }
+}
+
+}
diff --git a/Crypto/GHASH.cs b/Crypto/GHASH.cs
new file mode 100644 (file)
index 0000000..7a5fe3c
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * GHASH implementation using integer multiplications (32->64). It is
+ * constant-time, provided that the platform multiplication opcode is
+ * constant-time.
+ */
+
+public sealed class GHASH {
+
+       /*
+        * Compute GHASH over the provided data. The y[] array is
+        * updated, using the h[] secret value. If the data length
+        * is not a multiple of 16 bytes, then it is right-padded
+        * with zeros.
+        */
+       public static void Run(byte[] y, byte[] h, byte[] data)
+       {
+               Run(y, h, data, 0, data.Length);
+       }
+
+       /*
+        * Compute GHASH over the provided data. The y[] array is
+        * updated, using the h[] secret value. If the data length
+        * is not a multiple of 16 bytes, then it is right-padded
+        * with zeros.
+        */
+       public static void Run(byte[] y, byte[] h,
+               byte[] data, int off, int len)
+       {
+               uint y0, y1, y2, y3;
+               uint h0, h1, h2, h3;
+               y3 = Dec32be(y,  0);
+               y2 = Dec32be(y,  4);
+               y1 = Dec32be(y,  8);
+               y0 = Dec32be(y, 12);
+               h3 = Dec32be(h,  0);
+               h2 = Dec32be(h,  4);
+               h1 = Dec32be(h,  8);
+               h0 = Dec32be(h, 12);
+
+               while (len > 0) {
+                       /*
+                        * Decode the next block and add it (XOR) into the
+                        * current state.
+                        */
+                       if (len >= 16) {
+                               y3 ^= Dec32be(data, off);
+                               y2 ^= Dec32be(data, off + 4);
+                               y1 ^= Dec32be(data, off + 8);
+                               y0 ^= Dec32be(data, off + 12);
+                       } else {
+                               y3 ^= Dec32bePartial(data, off +  0, len -  0);
+                               y2 ^= Dec32bePartial(data, off +  4, len -  4);
+                               y1 ^= Dec32bePartial(data, off +  8, len -  8);
+                               y0 ^= Dec32bePartial(data, off + 12, len - 12);
+                       }
+                       off += 16;
+                       len -= 16;
+
+                       /*
+                        * We multiply two 128-bit field elements with
+                        * two Karatsuba levels, to get down to nine
+                        * 32->64 multiplications.
+                        */
+                       uint a0 = y0;
+                       uint b0 = h0;
+                       uint a1 = y1;
+                       uint b1 = h1;
+                       uint a2 = a0 ^ a1;
+                       uint b2 = b0 ^ b1;
+
+                       uint a3 = y2;
+                       uint b3 = h2;
+                       uint a4 = y3;
+                       uint b4 = h3;
+                       uint a5 = a3 ^ a4;
+                       uint b5 = b3 ^ b4;
+
+                       uint a6 = a0 ^ a3;
+                       uint b6 = b0 ^ b3;
+                       uint a7 = a1 ^ a4;
+                       uint b7 = b1 ^ b4;
+                       uint a8 = a6 ^ a7;
+                       uint b8 = b6 ^ b7;
+
+                       ulong z0 = BMul(a0, b0);
+                       ulong z1 = BMul(a1, b1);
+                       ulong z2 = BMul(a2, b2);
+                       ulong z3 = BMul(a3, b3);
+                       ulong z4 = BMul(a4, b4);
+                       ulong z5 = BMul(a5, b5);
+                       ulong z6 = BMul(a6, b6);
+                       ulong z7 = BMul(a7, b7);
+                       ulong z8 = BMul(a8, b8);
+
+                       z2 ^= z0 ^ z1;
+                       z0 ^= z2 << 32;
+                       z1 ^= z2 >> 32;
+
+                       z5 ^= z3 ^ z4;
+                       z3 ^= z5 << 32;
+                       z4 ^= z5 >> 32;
+
+                       z8 ^= z6 ^ z7;
+                       z6 ^= z8 << 32;
+                       z7 ^= z8 >> 32;
+
+                       z6 ^= z0 ^ z3;
+                       z7 ^= z1 ^ z4;
+                       z1 ^= z6;
+                       z3 ^= z7;
+
+                       /*
+                        * 255-bit product is now in z4:z3:z1:z0. Since
+                        * the GHASH specification uses a "reversed"
+                        * notation, our 255-bit result must be shifted
+                        * by 1 bit to the left.
+                        */
+                       z4 = (z4 << 1) | (z3 >> 63);
+                       z3 = (z3 << 1) | (z1 >> 63);
+                       z1 = (z1 << 1) | (z0 >> 63);
+                       z0 = (z0 << 1);
+
+                       /*
+                        * Apply reduction modulo the degree-128
+                        * polynomial that defines the field.
+                        */
+                       z3 ^= z0 ^ (z0 >> 1) ^ (z0 >> 2) ^ (z0 >> 7);
+                       z1 ^= (z0 << 63) ^ (z0 << 62) ^ (z0 << 57);
+                       z4 ^= z1 ^ (z1 >> 1) ^ (z1 >> 2) ^ (z1 >> 7);
+                       z3 ^= (z1 << 63) ^ (z1 << 62) ^ (z1 << 57);
+
+                       /*
+                        * The reduced result is the new "y" state.
+                        */
+                       y0 = (uint)z3;
+                       y1 = (uint)(z3 >> 32);
+                       y2 = (uint)z4;
+                       y3 = (uint)(z4 >> 32);
+               }
+
+               Enc32be(y3, y,  0);
+               Enc32be(y2, y,  4);
+               Enc32be(y1, y,  8);
+               Enc32be(y0, y, 12);
+       }
+
+       static ulong BMul(uint x, uint y)
+       {
+               ulong x0, x1, x2, x3;
+               ulong y0, y1, y2, y3;
+               ulong z0, z1, z2, z3;
+               x0 = (ulong)(x & 0x11111111);
+               x1 = (ulong)(x & 0x22222222);
+               x2 = (ulong)(x & 0x44444444);
+               x3 = (ulong)(x & 0x88888888);
+               y0 = (ulong)(y & 0x11111111);
+               y1 = (ulong)(y & 0x22222222);
+               y2 = (ulong)(y & 0x44444444);
+               y3 = (ulong)(y & 0x88888888);
+               z0 = (x0 * y0) ^ (x1 * y3) ^ (x2 * y2) ^ (x3 * y1);
+               z1 = (x0 * y1) ^ (x1 * y0) ^ (x2 * y3) ^ (x3 * y2);
+               z2 = (x0 * y2) ^ (x1 * y1) ^ (x2 * y0) ^ (x3 * y3);
+               z3 = (x0 * y3) ^ (x1 * y2) ^ (x2 * y1) ^ (x3 * y0);
+               z0 &= 0x1111111111111111;
+               z1 &= 0x2222222222222222;
+               z2 &= 0x4444444444444444;
+               z3 &= 0x8888888888888888;
+               return z0 | z1 | z2 | z3;
+       }
+
+       static uint Dec32be(byte[] buf, int off)
+       {
+               return ((uint)buf[off + 0] << 24)
+                       | ((uint)buf[off + 1] << 16)
+                       | ((uint)buf[off + 2] << 8)
+                       | (uint)buf[off + 3];
+       }
+
+       static void Enc32be(uint x, byte[] buf, int off)
+       {
+               buf[off + 0] = (byte)(x >> 24);
+               buf[off + 1] = (byte)(x >> 16);
+               buf[off + 2] = (byte)(x >> 8);
+               buf[off + 3] = (byte)x;
+       }
+
+       static uint Dec32bePartial(byte[] buf, int off, int len)
+       {
+               if (len >= 4) {
+                       return ((uint)buf[off + 0] << 24)
+                               | ((uint)buf[off + 1] << 16)
+                               | ((uint)buf[off + 2] << 8)
+                               | (uint)buf[off + 3];
+               } else if (len >= 3) {
+                       return ((uint)buf[off + 0] << 24)
+                               | ((uint)buf[off + 1] << 16)
+                               | ((uint)buf[off + 2] << 8);
+               } else if (len >= 2) {
+                       return ((uint)buf[off + 0] << 24)
+                               | ((uint)buf[off + 1] << 16);
+               } else if (len >= 1) {
+                       return ((uint)buf[off + 0] << 24);
+               } else {
+                       return 0;
+               }
+       }
+}
+
+}
diff --git a/Crypto/HMAC.cs b/Crypto/HMAC.cs
new file mode 100644 (file)
index 0000000..fcb1e29
--- /dev/null
@@ -0,0 +1,366 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Implementation of HMAC (RFC 2104).
+ */
+
+public sealed class HMAC {
+
+       IDigest h;
+       byte[] key;
+       int keyLen;
+       byte[] tmp, tmpCT;
+       ulong dataLen;
+
+       /*
+        * Create a new instance, using the provided hash function. The
+        * hash function instance will be used internally.
+        */
+       public HMAC(IDigest h) : this(h, true)
+       {
+       }
+
+       private HMAC(IDigest h, bool doReset)
+       {
+               this.h = h;
+               int n = h.BlockSize;
+               if (n < h.DigestSize) {
+                       throw new ArgumentException(
+                               "invalid hash function for HMAC");
+               }
+               key = new byte[n];
+               keyLen = 0;
+               tmp = new byte[h.DigestSize];
+               tmpCT = new byte[h.DigestSize];
+               if (doReset) {
+                       Reset();
+               }
+       }
+
+       /*
+        * Duplicate this engine. The returned instance inherits the
+        * current state (key, data already processed...) but is
+        * thereafter independent.
+        */
+       public HMAC Dup()
+       {
+               HMAC hm = new HMAC(h.Dup(), false);
+               Array.Copy(key, 0, hm.key, 0, keyLen);
+               hm.keyLen = keyLen;
+               hm.dataLen = dataLen;
+               return hm;
+       }
+
+       /*
+        * Get the HMAC output size (in bytes).
+        */
+       public int MACSize {
+               get {
+                       return h.DigestSize;
+               }
+       }
+
+       /*
+        * Set the HMAC key. This implies a call to Reset() (thus,
+        * the key must be set before data begins to be inserted).
+        */
+       public void SetKey(byte[] key)
+       {
+               SetKey(key, 0, key.Length);
+       }
+
+       /*
+        * Set the HMAC key ('len' bytes, starting at offset 'off' in key[]).
+        */
+       public void SetKey(byte[] key, int off, int len)
+       {
+               h.Reset();
+               if (len > h.BlockSize) {
+                       h.Update(key, off, len);
+                       h.DoFinal(this.key, 0);
+                       keyLen = h.DigestSize;
+               } else {
+                       Array.Copy(key, off, this.key, 0, len);
+                       keyLen = len;
+               }
+               Reset();
+       }
+
+       /*
+        * Add one byte to the input to HMAC.
+        */
+       public void Update(byte b)
+       {
+               h.Update(b);
+               dataLen ++;
+       }
+
+       /*
+        * Add some bytes to the input to HMAC.
+        */
+       public void Update(byte[] buf)
+       {
+               h.Update(buf, 0, buf.Length);
+       }
+
+       /*
+        * Add some bytes to the input to HMAC ('len' bytes from buf[],
+        * starting at offset 'off').
+        */
+       public void Update(byte[] buf, int off, int len)
+       {
+               h.Update(buf, off, len);
+               dataLen += (ulong)len;
+       }
+
+       /*
+        * Compute the HMAC value; it is written in outBuf[] at
+        * offset 'off'. The engine is also reset (as if Reset()
+        * was called).
+        */
+       public void DoFinal(byte[] outBuf, int off)
+       {
+               h.DoFinal(tmp, 0);
+               ProcessKey(0x5C);
+               h.Update(tmp);
+               h.DoFinal(outBuf, off);
+               Reset();
+       }
+
+       /*
+        * Compute the HMAC value; it is written to a newly allocated
+        * array, which is returned. The engine is also reset (as if
+        * Reset() was called).
+        */
+       public byte[] DoFinal()
+       {
+               byte[] r = new byte[h.DigestSize];
+               DoFinal(r, 0);
+               return r;
+       }
+
+       /*
+        * Reset the HMAC engine. This forgets all previously input
+        * data, but reuses the currently set key.
+        */
+       public void Reset()
+       {
+               h.Reset();
+               ProcessKey(0x36);
+               dataLen = 0;
+       }
+
+       /*
+        * Process some bytes, then compute the output.
+        *
+        * This function is supposed to implement the processing in
+        * constant time (and thus constant memory access pattern) for
+        * all values of 'len' between 'minLen' and 'maxLen'
+        * (inclusive). This function works only for the supported
+        * underlying hash functions (MD5, SHA-1 and the SHA-2
+        * functions).
+        *
+        * The source array (buf[]) must contain at least maxLen bytes
+        * (starting at offset 'off'); they will all be read.
+        */
+       public void ComputeCT(byte[] buf, int off, int len,
+               int minLen, int maxLen, byte[] outBuf, int outOff)
+       {
+               /*
+                * Padding is 0x80, followed by 0 to 63 bytes of value
+                * 0x00 (up to 127 bytes for SHA-384 and SHA-512), then
+                * the input bit length expressed over 64 bits
+                * (little-endian for MD5, big-endian for
+                * the SHA-* functions)(for SHA-384 and SHA-512, this is
+                * 128 bits).
+                *
+                * Note that we only support bit lengths that fit on
+                * 64 bits, so we can handle SHA-384/SHA-512 padding
+                * almost as if it was the same as SHA-256; we just have
+                * to take care of the larger blocks (128 bytes instead
+                * of 64) and the larger minimal overhead (17 bytes
+                * instead of 9 bytes).
+                *
+                * be   true for big-endian length encoding
+                * bs   block size, in bytes (must be a power of 2)
+                * po   padding overhead (0x80 byte and length encoding)
+                */
+               bool be;
+               int bs, po;
+               if (h is MD5) {
+                       be = false;
+                       bs = 64;
+                       po = 9;
+               } else if ((h is SHA1) || (h is SHA2Small)) {
+                       be = true;
+                       bs = 64;
+                       po = 9;
+               } else if (h is SHA2Big) {
+                       be = true;
+                       bs = 128;
+                       po = 17;
+               } else {
+                       throw new NotSupportedException();
+               }
+
+               /*
+                * Method implemented here is inspired from the one
+                * described there:
+                * https://www.imperialviolet.org/2013/02/04/luckythirteen.html
+                */
+
+               /*
+                * We compute the data bit length; let's not forget
+                * the initial first block (the one with the HMAC key).
+                */
+               ulong bitLen = ((ulong)bs + dataLen + (ulong)len) << 3;
+
+               /*
+                * All complete blocks before minLen can be processed
+                * efficiently.
+                */
+               ulong nDataLen = (dataLen + (ulong)minLen) & ~(ulong)(bs - 1);
+               if (nDataLen > dataLen) {
+                       int zlen = (int)(nDataLen - dataLen);
+                       h.Update(buf, off, (int)(nDataLen - dataLen));
+                       dataLen = nDataLen;
+                       off += zlen;
+                       len -= zlen;
+                       maxLen -= zlen;
+               }
+
+               /*
+                * At that point:
+                * -- dataLen contains the number of bytes already processed
+                * (in total, not counting the initial key block).
+                * -- We must input 'len' bytes, which may be up to 'maxLen'
+                * (inclusive).
+                *
+                * We compute kr, kl, kz and km:
+                *  kr   number of input bytes already in the current block
+                *  km   index of the first byte after the end of the last
+                *       padding block, if 'len' is equal to 'maxLen'
+                *  kz   index of the last byte of the actual last padding
+                *       block
+                *  kl   index of the start of the encoded length
+                */
+               int kr = (int)dataLen & (bs - 1);
+               int kz = ((kr + len + po + bs - 1) & ~(bs - 1)) - 1 - kr;
+               int kl = kz - 7;
+               int km = ((kr + maxLen + po + bs - 1) & ~(bs - 1)) - kr;
+
+               /*
+                * We must process km bytes. For index i from 0 to km-1:
+                *   d is from data[] if i < maxLen, 0x00 otherwise
+                *   e is an encoded length byte or 0x00, depending on i
+                * These tests do not depend on the actual length, so
+                * they need not be constant-time.
+                *
+                * Actual input byte is:
+                *   d      if i < len
+                *   0x80   if i == len
+                *   0x00   if i > len and i < kl
+                *   e      if i >= kl
+                *
+                * We extract hash state whenever we reach a full block;
+                * we keep it only if i == kz.
+                */
+               int hlen = h.DigestSize;
+               for (int k = 0; k < hlen; k ++) {
+                       tmp[k] = 0;
+               }
+               for (int i = 0; i < km; i ++) {
+                       int d = (i < maxLen) ? buf[off + i] : 0x00;
+                       int e;
+                       int j = (kr + i) & (bs - 1);
+                       if (j >= (bs - 8)) {
+                               int k = (j - (bs - 8)) << 3;
+                               if (be) {
+                                       e = (int)(bitLen >> (56 - k));
+                               } else {
+                                       e = (int)(bitLen >> k);
+                               }
+                               e &= 0xFF;
+                       } else {
+                               e = 0x00;
+                       }
+
+                       /*
+                        * x0 is 0x80 if i == len; otherwise it is d.
+                        */
+                       int z = i - len;
+                       int x0 = 0x80 ^ (((z | -z) >> 31) & (0x80 ^ d));
+
+                       /*
+                        * x1 is e if i >= kl; otherwise it is 0x00.
+                        */
+                       int x1 = e & ~((i - kl) >> 31);
+
+                       /*
+                        * We input x0 if i <= len, x1 otherwise.
+                        */
+                       h.Update((byte)(x0 ^ (((len - i) >> 31) & (x0 ^ x1))));
+
+                       /*
+                        * Get current state if we are at the end of a block,
+                        * and keep it if i == kz.
+                        */
+                       if (j == (bs - 1)) {
+                               h.CurrentState(tmpCT, 0);
+                               z = i - kz;
+                               z = ~((z | -z) >> 31);
+                               for (int k = 0; k < hlen; k ++) {
+                                       tmp[k] |= (byte)(z & tmpCT[k]);
+                               }
+                       }
+               }
+
+               /*
+                * We got the hash output in tmp[]; we must complete
+                * the HMAC computation.
+                */
+               h.Reset();
+               ProcessKey(0x5C);
+               h.Update(tmp);
+               h.DoFinal(outBuf, outOff);
+               Reset();
+       }
+
+       void ProcessKey(byte pad)
+       {
+               for (int i = 0; i < keyLen; i ++) {
+                       h.Update((byte)(key[i] ^ pad));
+               }
+               for (int i = h.BlockSize - keyLen; i > 0; i --) {
+                       h.Update(pad);
+               }
+       }
+}
+
+}
diff --git a/Crypto/HMAC_DRBG.cs b/Crypto/HMAC_DRBG.cs
new file mode 100644 (file)
index 0000000..abef663
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Implementation of HMAC_DRBG (NIST SP800-90A).
+ *
+ * This class provides HMAC_DRBG as a deterministic PRNG from a given
+ * seed. Once a seed is set, chunks of data are obtained with
+ * GetBytes(). The GetBytes() methods can be called several times;
+ * the internal state is updated after each call. Setting a new seed
+ * resets the internal state.
+ */
+
+public sealed class HMAC_DRBG {
+
+       HMAC hm;
+       byte[] K, V;
+       bool seeded;
+
+       /*
+        * Create the instance over the provided hash function
+        * implementation. The digest instance is linked in and will
+        * be used repeatedly. The engine is not seeded yet.
+        */
+       public HMAC_DRBG(IDigest h)
+       {
+               hm = new HMAC(h.Dup());
+               int len = h.DigestSize;
+               K = new byte[len];
+               V = new byte[len];
+               seeded = false;
+               Reset();
+       }
+
+       /*
+        * Reset the engine. A seed will have to be provided before
+        * generating pseudorandom bytes.
+        */
+       public void Reset()
+       {
+               for (int i = 0; i < K.Length; i ++) {
+                       K[i] = 0x00;
+                       V[i] = 0x01;
+               }
+               hm.SetKey(K);
+       }
+
+       /*
+        * Reset the engine with the provided seed.
+        */
+       public void SetSeed(byte[] seed)
+       {
+               Reset();
+               Update(seed);
+       }
+
+       /*
+        * Reset the engine with the provided seed.
+        */
+       public void SetSeed(byte[] seed, int off, int len)
+       {
+               Reset();
+               Update(seed, off, len);
+       }
+
+       /*
+        * Inject an additional seed. This may be null, in which case
+        * the state is modified but the engine is not marked as "seeded"
+        * (if it was not already marked so).
+        */
+       public void Update(byte[] seed)
+       {
+               if (seed != null) {
+                       Update(seed, 0, seed.Length);
+               } else {
+                       Update(null, 0, 0);
+               }
+       }
+
+       /*
+        * Inject an additional seed. If the seed length is 0, then the
+        * state is modified, but the engine is not marked as "seeded"
+        * (if it was not already marked so).
+        */
+       public void Update(byte[] seed, int off, int len)
+       {
+               /* K = HMAC_K(V || 0x00 || seed) */
+               hm.Update(V);
+               hm.Update((byte)0x00);
+               hm.Update(seed, off, len);
+               hm.DoFinal(K, 0);
+               hm.SetKey(K);
+
+               /* V = HMAC_K(V) */
+               hm.Update(V);
+               hm.DoFinal(V, 0);
+
+               /*
+                * Stop there if the additional seed is empty.
+                */
+               if (len == 0) {
+                       return;
+               }
+
+               /* K = HMAC_K(V || 0x01 || seed) */
+               hm.Update(V);
+               hm.Update((byte)0x01);
+               hm.Update(seed, off, len);
+               hm.DoFinal(K, 0);
+               hm.SetKey(K);
+
+               /* V = HMAC_K(V) */
+               hm.Update(V);
+               hm.DoFinal(V, 0);
+
+               /*
+                * We get there only if a non-empty seed is used.
+                */
+               seeded = true;
+       }
+
+       /*
+        * Generate some pseudorandom bytes. The engine MUST have been
+        * seeded.
+        */
+       public void GetBytes(byte[] buf)
+       {
+               GetBytes(buf, 0, buf.Length);
+       }
+
+       /*
+        * Generate some pseudorandom bytes. The engine MUST have been
+        * seeded.
+        */
+       public void GetBytes(byte[] buf, int off, int len)
+       {
+               if (!seeded) {
+                       throw new CryptoException(
+                               "HMAC_DRBG engine was not seeded");
+               }
+               while (len > 0) {
+                       /* V = HMAC_K(V) */
+                       hm.Update(V);
+                       hm.DoFinal(V, 0);
+                       int clen = Math.Min(V.Length, len);
+                       Array.Copy(V, 0, buf, off, clen);
+                       off += clen;
+                       len -= clen;
+               }
+
+               /* K = HMAC_K(V || 0x00) */
+               hm.Update(V);
+               hm.Update((byte)0x00);
+               hm.DoFinal(K, 0);
+               hm.SetKey(K);
+
+               /* V = HMAC_K(V) */
+               hm.Update(V);
+               hm.DoFinal(V, 0);
+       }
+}
+
+}
diff --git a/Crypto/IBlockCipher.cs b/Crypto/IBlockCipher.cs
new file mode 100644 (file)
index 0000000..6a51b99
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Interface for a block cipher implementation. Each instance has a
+ * state, which contains (at least) the current secret key, but may also
+ * have other internal values. Thereby, instances are not thread-safe.
+ * The Dup() method may be used to "clone" an instance into a new,
+ * independent instance that starts its life configured with the same
+ * secret key.
+ */
+
+public interface IBlockCipher {
+
+       /*
+        * Get the block size in bytes.
+        */
+       int BlockSize { get; }
+
+       /*
+        * Set the key.
+        */
+       void SetKey(byte[] key);
+
+       /*
+        * Set the key.
+        */
+       void SetKey(byte[] key, int off, int len);
+
+       /*
+        * Encrypt one block.
+        */
+       void BlockEncrypt(byte[] buf);
+
+       /*
+        * Encrypt one block.
+        */
+       void BlockEncrypt(byte[] buf, int off);
+
+       /*
+        * Decrypt one block.
+        */
+       void BlockDecrypt(byte[] buf);
+
+       /*
+        * Encrypt one block.
+        */
+       void BlockDecrypt(byte[] buf, int off);
+
+       /*
+        * Do CBC encryption. There is no padding; the source array
+        * must already have a length multiple of the block size.
+        * The provided iv[] array must have the same length as a
+        * block. The data is encrypted in-place. The iv[] array is
+        * unmodified.
+        */
+       void CBCEncrypt(byte[] iv, byte[] data);
+
+       /*
+        * Do CBC encryption. There is no padding; the source array
+        * must already have a length multiple of the block size.
+        * The provided iv[] array must have the same length as a
+        * block. The data is encrypted in-place. The iv[] array is
+        * unmodified.
+        */
+       void CBCEncrypt(byte[] iv, byte[] data, int off, int len);
+
+       /*
+        * Do CBC decryption. The source array must have a length
+        * multiple of the block size; no attempt at padding removal is
+        * performed. The provided iv[] array must have the same length
+        * as a block. The data is decrypted in-place. The iv[] array is
+        * unmodified.
+        */
+       void CBCDecrypt(byte[] iv, byte[] data);
+
+       /*
+        * Do CBC decryption. The source array must have a length
+        * multiple of the block size; no attempt at padding removal is
+        * performed. The provided iv[] array must have the same length
+        * as a block. The data is decrypted in-place. The iv[] array is
+        * unmodified.
+        */
+       void CBCDecrypt(byte[] iv, byte[] data, int off, int len);
+
+       /*
+        * Do CTR encryption or decryption. This implements the variant
+        * used in GCM:
+        *  - IV length is 4 bytes less than the block length
+        *  - The block counter is used for the last 4 bytes of the
+        *    block input; big-endian encoding is used.
+        *  - Counter arithmetic is done modulo 2^32.
+        *
+        * The starting counter value is provided as parameter; the new
+        * counter value is returned. This allows computing a long CTR
+        * run in several chunks, as long as all chunks (except possibly
+        * the last one) have a length which is multiple of the block size.
+        */
+       uint CTRRun(byte[] iv, uint cc, byte[] data);
+
+       /*
+        * Do CTR encryption or decryption. This implements the variant
+        * used in GCM:
+        *  - IV length is 4 bytes less than the block length
+        *  - The block counter is used for the last 4 bytes of the
+        *    block input; big-endian encoding is used.
+        *  - Counter arithmetic is done modulo 2^32.
+        *
+        * The starting counter value is provided as parameter; the new
+        * counter value is returned. This allows computing a long CTR
+        * run in several chunks, as long as all chunks (except possibly
+        * the last one) have a length which is multiple of the block size.
+        */
+       uint CTRRun(byte[] iv, uint cc, byte[] data, int off, int len);
+
+       /*
+        * Duplicate this engine. This creates a new, independent
+        * instance that implements the same function, and starts with
+        * the same currently set key.
+        */
+       IBlockCipher Dup();
+}
+
+}
diff --git a/Crypto/IDigest.cs b/Crypto/IDigest.cs
new file mode 100644 (file)
index 0000000..87fb4bf
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This interface qualifies a hash function implementation.
+ */
+
+public interface IDigest {
+
+       /*
+        * Get the hash function symbolic name.
+        */
+       string Name { get; }
+
+       /*
+        * Get the hash function output size, in bytes.
+        */
+       int DigestSize { get; }
+
+       /*
+        * Get the hash function block size, in bytes (the block
+        * size is used in the definition of HMAC).
+        */
+       int BlockSize { get; }
+
+       /*
+        * Add one byte to the current input.
+        */
+       void Update(byte b);
+
+       /*
+        * Add some bytes to the current input.
+        */
+       void Update(byte[] buf);
+
+       /*
+        * Add some bytes to the current input ('len' bytes from buf[],
+        * starting at offset 'off').
+        */
+       void Update(byte[] buf, int off, int len);
+
+       /*
+        * Finalize the hash computation and write the output in the
+        * provided outBuf[] array (starting at offset 'off'). This
+        * instance is also automatically reset (as if by a Reset() call).
+        */
+       void DoFinal(byte[] outBuf, int off);
+
+       /*
+        * Finalize the hash computation and write the output into a
+        * newly allocated buffer, which is returned. This instance
+        * is also automatically reset (as if by a Reset() call).
+        */
+       byte[] DoFinal();
+
+       /*
+        * Finalize the current hash computation but keep it active;
+        * this thus returns the hash value computed over the input
+        * bytes injected so far, but new bytes may be added afterwards.
+        * The output is written in outBuf[] at offset 'off'.
+        */
+       void DoPartial(byte[] outBuf, int off);
+
+       /*
+        * Finalize the current hash computation but keep it active;
+        * this thus returns the hash value computed over the input
+        * bytes injected so far, but new bytes may be added afterwards.
+        * The output is written into a newly allocated array, which
+        * is returned.
+        */
+       byte[] DoPartial();
+
+       /*
+        * Encode the current running state into the provided buffer.
+        * This is defined for functions that employ an internal padding
+        * but no special finalization step (e.g. MD5, SHA-1, SHA-256);
+        * the running state is the one resulting from the last
+        * processed block.
+        *
+        * Note: for SHA-224, SHA-384 and similar functions, the current
+        * state as returned by this method will be truncated to the
+        * actual hash output length.
+        */
+       void CurrentState(byte[] outBuf, int off);
+
+       /*
+        * Reset the internal state, to start a new computation. This
+        * can be called at any time.
+        */
+       void Reset();
+
+       /*
+        * Duplicate this engine. This creates a new, independent
+        * instance that implements the same function, and starts with
+        * the current state.
+        */
+       IDigest Dup();
+
+       /*
+        * Compute the hash of a given input in one call; this does NOT
+        * change the internal state.
+        */
+       byte[] Hash(byte[] buf);
+
+       /*
+        * Compute the hash of a given input in one call; this does NOT
+        * change the internal state.
+        */
+       byte[] Hash(byte[] buf, int off, int len);
+}
+
+}
diff --git a/Crypto/IPrivateKey.cs b/Crypto/IPrivateKey.cs
new file mode 100644 (file)
index 0000000..f29f81d
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Interface of an asymmetric private key.
+ */
+
+public interface IPrivateKey {
+
+       /*
+        * Private key size, expressed in bits. Exact definition of the
+        * "size" depends on the key type.
+        */
+       int KeySizeBits { get; }
+
+       /*
+        * Algorithm name (for display).
+        */
+       string AlgorithmName { get; }
+
+       /*
+        * Get (or compute) the public key for this private key.
+        */
+       IPublicKey PublicKey { get; }
+
+       /*
+        * Perform algorithm-dependent consistency checks. These
+        * checks may be relatively expensive. On any error, a
+        * CryptoException is thrown.
+        */
+       void CheckValid();
+}
+
+}
diff --git a/Crypto/IPublicKey.cs b/Crypto/IPublicKey.cs
new file mode 100644 (file)
index 0000000..150e190
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Interface for an asymmetric public key.
+ */
+
+public interface IPublicKey {
+
+       /*
+        * Public key size, expressed in bits. Exact definition of the
+        * "size" depends on the key type.
+        */
+       int KeySizeBits { get; }
+
+       /*
+        * Algorithm name (for display).
+        */
+       string AlgorithmName { get; }
+
+       /*
+        * Perform algorithm-dependent consistency checks. These
+        * checks may be relatively expensive. On any error, a
+        * CryptoException is thrown.
+        */
+       void CheckValid();
+
+       /*
+        * Public keys ought to be comparable for equality.
+        */
+       bool Equals(object obj);
+       int GetHashCode();
+}
+
+}
diff --git a/Crypto/MD5.cs b/Crypto/MD5.cs
new file mode 100644 (file)
index 0000000..7b083f3
--- /dev/null
@@ -0,0 +1,391 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * MD5 implementation. MD5 is described in RFC 1321.
+ *
+ * WARNING: as a cryptographic hash function, MD5 turned out to be
+ * very weak with regards to collisions. Use with care.
+ */
+
+public sealed class MD5 : DigestCore {
+
+       const int BLOCK_LEN = 64;
+
+       uint A, B, C, D;
+       byte[] block, saveBlock;
+       int ptr;
+       ulong byteCount;
+
+       /*
+        * Create a new instance. It is ready to process data bytes.
+        */
+       public MD5()
+       {
+               block = new byte[BLOCK_LEN];
+               saveBlock = new byte[BLOCK_LEN];
+               Reset();
+       }
+
+       /* see IDigest */
+       public override string Name {
+               get {
+                       return "MD5";
+               }
+       }
+
+       /* see IDigest */
+       public override int DigestSize {
+               get {
+                       return 16;
+               }
+       }
+
+       /* see IDigest */
+       public override int BlockSize {
+               get {
+                       return 64;
+               }
+       }
+
+       /* see IDigest */
+       public override void Reset()
+       {
+               A = 0x67452301;
+               B = 0xEFCDAB89;
+               C = 0x98BADCFE;
+               D = 0x10325476;
+               byteCount = 0;
+               ptr = 0;
+       }
+
+       /* see IDigest */
+       public override void Update(byte b)
+       {
+               block[ptr ++] = b;
+               byteCount ++;
+               if (ptr == BLOCK_LEN) {
+                       ProcessBlock();
+               }
+       }
+
+       /* see IDigest */
+       public override void Update(byte[] buf, int off, int len)
+       {
+               if (len < 0) {
+                       throw new ArgumentException("negative chunk length");
+               }
+               byteCount += (ulong)len;
+               while (len > 0) {
+                       int clen = Math.Min(len, BLOCK_LEN - ptr);
+                       Array.Copy(buf, off, block, ptr, clen);
+                       off += clen;
+                       len -= clen;
+                       ptr += clen;
+                       if (ptr == BLOCK_LEN) {
+                               ProcessBlock();
+                       }
+               }
+       }
+
+       /* see IDigest */
+       public override void DoPartial(byte[] outBuf, int off)
+       {
+               /*
+                * Save current state.
+                */
+               uint saveA = A;
+               uint saveB = B;
+               uint saveC = C;
+               uint saveD = D;
+               int savePtr = ptr;
+               Array.Copy(block, 0, saveBlock, 0, savePtr);
+
+               /*
+                * Add padding. This may involve processing an extra block.
+                */
+               block[ptr ++] = 0x80;
+               if (ptr > BLOCK_LEN - 8) {
+                       for (int j = ptr; j < BLOCK_LEN; j ++) {
+                               block[j] = 0;
+                       }
+                       ProcessBlock();
+               }
+               for (int j = ptr; j < (BLOCK_LEN - 8); j ++) {
+                       block[j] = 0;
+               }
+               ulong x = byteCount << 3;
+               Enc32le((uint)x, block, BLOCK_LEN - 8);
+               Enc32le((uint)(x >> 32), block, BLOCK_LEN - 4);
+
+               /*
+                * Process final block and encode result.
+                */
+               ProcessBlock();
+               Enc32le(A, outBuf, off);
+               Enc32le(B, outBuf, off + 4);
+               Enc32le(C, outBuf, off + 8);
+               Enc32le(D, outBuf, off + 12);
+
+               /*
+                * Restore current state.
+                */
+               Array.Copy(saveBlock, 0, block, 0, savePtr);
+               A = saveA;
+               B = saveB;
+               C = saveC;
+               D = saveD;
+               ptr = savePtr;
+       }
+
+       /* see IDigest */
+       public override IDigest Dup()
+       {
+               MD5 h = new MD5();
+               h.A = A;
+               h.B = B;
+               h.C = C;
+               h.D = D;
+               h.ptr = ptr;
+               h.byteCount = byteCount;
+               Array.Copy(block, 0, h.block, 0, ptr);
+               return h;
+       }
+
+       /* see IDigest */
+       public override void CurrentState(byte[] outBuf, int off)
+       {
+               Enc32le(A, outBuf, off);
+               Enc32le(B, outBuf, off + 4);
+               Enc32le(C, outBuf, off + 8);
+               Enc32le(D, outBuf, off + 12);
+       }
+
+       void ProcessBlock()
+       {
+               /*
+                * Decode input block (sixteen 32-bit words).
+                */
+               uint x0 = Dec32le(block,  0);
+               uint x1 = Dec32le(block,  4);
+               uint x2 = Dec32le(block,  8);
+               uint x3 = Dec32le(block, 12);
+               uint x4 = Dec32le(block, 16);
+               uint x5 = Dec32le(block, 20);
+               uint x6 = Dec32le(block, 24);
+               uint x7 = Dec32le(block, 28);
+               uint x8 = Dec32le(block, 32);
+               uint x9 = Dec32le(block, 36);
+               uint xA = Dec32le(block, 40);
+               uint xB = Dec32le(block, 44);
+               uint xC = Dec32le(block, 48);
+               uint xD = Dec32le(block, 52);
+               uint xE = Dec32le(block, 56);
+               uint xF = Dec32le(block, 60);
+
+               /*
+                * Read state words.
+                */
+               uint wa = A;
+               uint wb = B;
+               uint wc = C;
+               uint wd = D;
+               uint tmp;
+
+               /*
+                * Rounds 0 to 15.
+                */
+               tmp = wa + (wd ^ (wb & (wc ^ wd))) + x0 + 0xD76AA478;
+               wa = wb + ((tmp << 7) | (tmp >> 25));
+               tmp = wd + (wc ^ (wa & (wb ^ wc))) + x1 + 0xE8C7B756;
+               wd = wa + ((tmp << 12) | (tmp >> 20));
+               tmp = wc + (wb ^ (wd & (wa ^ wb))) + x2 + 0x242070DB;
+               wc = wd + ((tmp << 17) | (tmp >> 15));
+               tmp = wb + (wa ^ (wc & (wd ^ wa))) + x3 + 0xC1BDCEEE;
+               wb = wc + ((tmp << 22) | (tmp >> 10));
+               tmp = wa + (wd ^ (wb & (wc ^ wd))) + x4 + 0xF57C0FAF;
+               wa = wb + ((tmp << 7) | (tmp >> 25));
+               tmp = wd + (wc ^ (wa & (wb ^ wc))) + x5 + 0x4787C62A;
+               wd = wa + ((tmp << 12) | (tmp >> 20));
+               tmp = wc + (wb ^ (wd & (wa ^ wb))) + x6 + 0xA8304613;
+               wc = wd + ((tmp << 17) | (tmp >> 15));
+               tmp = wb + (wa ^ (wc & (wd ^ wa))) + x7 + 0xFD469501;
+               wb = wc + ((tmp << 22) | (tmp >> 10));
+               tmp = wa + (wd ^ (wb & (wc ^ wd))) + x8 + 0x698098D8;
+               wa = wb + ((tmp << 7) | (tmp >> 25));
+               tmp = wd + (wc ^ (wa & (wb ^ wc))) + x9 + 0x8B44F7AF;
+               wd = wa + ((tmp << 12) | (tmp >> 20));
+               tmp = wc + (wb ^ (wd & (wa ^ wb))) + xA + 0xFFFF5BB1;
+               wc = wd + ((tmp << 17) | (tmp >> 15));
+               tmp = wb + (wa ^ (wc & (wd ^ wa))) + xB + 0x895CD7BE;
+               wb = wc + ((tmp << 22) | (tmp >> 10));
+               tmp = wa + (wd ^ (wb & (wc ^ wd))) + xC + 0x6B901122;
+               wa = wb + ((tmp << 7) | (tmp >> 25));
+               tmp = wd + (wc ^ (wa & (wb ^ wc))) + xD + 0xFD987193;
+               wd = wa + ((tmp << 12) | (tmp >> 20));
+               tmp = wc + (wb ^ (wd & (wa ^ wb))) + xE + 0xA679438E;
+               wc = wd + ((tmp << 17) | (tmp >> 15));
+               tmp = wb + (wa ^ (wc & (wd ^ wa))) + xF + 0x49B40821;
+               wb = wc + ((tmp << 22) | (tmp >> 10));
+
+               /*
+                * Rounds 16 to 31.
+                */
+               tmp = wa + (wc ^ (wd & (wb ^ wc))) + x1 + 0xF61E2562;
+               wa = wb + ((tmp << 5) | (tmp >> 27));
+               tmp = wd + (wb ^ (wc & (wa ^ wb))) + x6 + 0xC040B340;
+               wd = wa + ((tmp << 9) | (tmp >> 23));
+               tmp = wc + (wa ^ (wb & (wd ^ wa))) + xB + 0x265E5A51;
+               wc = wd + ((tmp << 14) | (tmp >> 18));
+               tmp = wb + (wd ^ (wa & (wc ^ wd))) + x0 + 0xE9B6C7AA;
+               wb = wc + ((tmp << 20) | (tmp >> 12));
+               tmp = wa + (wc ^ (wd & (wb ^ wc))) + x5 + 0xD62F105D;
+               wa = wb + ((tmp << 5) | (tmp >> 27));
+               tmp = wd + (wb ^ (wc & (wa ^ wb))) + xA + 0x02441453;
+               wd = wa + ((tmp << 9) | (tmp >> 23));
+               tmp = wc + (wa ^ (wb & (wd ^ wa))) + xF + 0xD8A1E681;
+               wc = wd + ((tmp << 14) | (tmp >> 18));
+               tmp = wb + (wd ^ (wa & (wc ^ wd))) + x4 + 0xE7D3FBC8;
+               wb = wc + ((tmp << 20) | (tmp >> 12));
+               tmp = wa + (wc ^ (wd & (wb ^ wc))) + x9 + 0x21E1CDE6;
+               wa = wb + ((tmp << 5) | (tmp >> 27));
+               tmp = wd + (wb ^ (wc & (wa ^ wb))) + xE + 0xC33707D6;
+               wd = wa + ((tmp << 9) | (tmp >> 23));
+               tmp = wc + (wa ^ (wb & (wd ^ wa))) + x3 + 0xF4D50D87;
+               wc = wd + ((tmp << 14) | (tmp >> 18));
+               tmp = wb + (wd ^ (wa & (wc ^ wd))) + x8 + 0x455A14ED;
+               wb = wc + ((tmp << 20) | (tmp >> 12));
+               tmp = wa + (wc ^ (wd & (wb ^ wc))) + xD + 0xA9E3E905;
+               wa = wb + ((tmp << 5) | (tmp >> 27));
+               tmp = wd + (wb ^ (wc & (wa ^ wb))) + x2 + 0xFCEFA3F8;
+               wd = wa + ((tmp << 9) | (tmp >> 23));
+               tmp = wc + (wa ^ (wb & (wd ^ wa))) + x7 + 0x676F02D9;
+               wc = wd + ((tmp << 14) | (tmp >> 18));
+               tmp = wb + (wd ^ (wa & (wc ^ wd))) + xC + 0x8D2A4C8A;
+               wb = wc + ((tmp << 20) | (tmp >> 12));
+
+               /*
+                * Rounds 32 to 47.
+                */
+               tmp = wa + (wb ^ wc ^ wd) + x5 + 0xFFFA3942;
+               wa = wb + ((tmp << 4) | (tmp >> 28));
+               tmp = wd + (wa ^ wb ^ wc) + x8 + 0x8771F681;
+               wd = wa + ((tmp << 11) | (tmp >> 21));
+               tmp = wc + (wd ^ wa ^ wb) + xB + 0x6D9D6122;
+               wc = wd + ((tmp << 16) | (tmp >> 16));
+               tmp = wb + (wc ^ wd ^ wa) + xE + 0xFDE5380C;
+               wb = wc + ((tmp << 23) | (tmp >> 9));
+               tmp = wa + (wb ^ wc ^ wd) + x1 + 0xA4BEEA44;
+               wa = wb + ((tmp << 4) | (tmp >> 28));
+               tmp = wd + (wa ^ wb ^ wc) + x4 + 0x4BDECFA9;
+               wd = wa + ((tmp << 11) | (tmp >> 21));
+               tmp = wc + (wd ^ wa ^ wb) + x7 + 0xF6BB4B60;
+               wc = wd + ((tmp << 16) | (tmp >> 16));
+               tmp = wb + (wc ^ wd ^ wa) + xA + 0xBEBFBC70;
+               wb = wc + ((tmp << 23) | (tmp >> 9));
+               tmp = wa + (wb ^ wc ^ wd) + xD + 0x289B7EC6;
+               wa = wb + ((tmp << 4) | (tmp >> 28));
+               tmp = wd + (wa ^ wb ^ wc) + x0 + 0xEAA127FA;
+               wd = wa + ((tmp << 11) | (tmp >> 21));
+               tmp = wc + (wd ^ wa ^ wb) + x3 + 0xD4EF3085;
+               wc = wd + ((tmp << 16) | (tmp >> 16));
+               tmp = wb + (wc ^ wd ^ wa) + x6 + 0x04881D05;
+               wb = wc + ((tmp << 23) | (tmp >> 9));
+               tmp = wa + (wb ^ wc ^ wd) + x9 + 0xD9D4D039;
+               wa = wb + ((tmp << 4) | (tmp >> 28));
+               tmp = wd + (wa ^ wb ^ wc) + xC + 0xE6DB99E5;
+               wd = wa + ((tmp << 11) | (tmp >> 21));
+               tmp = wc + (wd ^ wa ^ wb) + xF + 0x1FA27CF8;
+               wc = wd + ((tmp << 16) | (tmp >> 16));
+               tmp = wb + (wc ^ wd ^ wa) + x2 + 0xC4AC5665;
+               wb = wc + ((tmp << 23) | (tmp >> 9));
+
+               /*
+                * Rounds 48 to 63.
+                */
+               tmp = wa + (wc ^ (wb | ~wd)) + x0 + 0xF4292244;
+               wa = wb + ((tmp << 6) | (tmp >> 26));
+               tmp = wd + (wb ^ (wa | ~wc)) + x7 + 0x432AFF97;
+               wd = wa + ((tmp << 10) | (tmp >> 22));
+               tmp = wc + (wa ^ (wd | ~wb)) + xE + 0xAB9423A7;
+               wc = wd + ((tmp << 15) | (tmp >> 17));
+               tmp = wb + (wd ^ (wc | ~wa)) + x5 + 0xFC93A039;
+               wb = wc + ((tmp << 21) | (tmp >> 11));
+               tmp = wa + (wc ^ (wb | ~wd)) + xC + 0x655B59C3;
+               wa = wb + ((tmp << 6) | (tmp >> 26));
+               tmp = wd + (wb ^ (wa | ~wc)) + x3 + 0x8F0CCC92;
+               wd = wa + ((tmp << 10) | (tmp >> 22));
+               tmp = wc + (wa ^ (wd | ~wb)) + xA + 0xFFEFF47D;
+               wc = wd + ((tmp << 15) | (tmp >> 17));
+               tmp = wb + (wd ^ (wc | ~wa)) + x1 + 0x85845DD1;
+               wb = wc + ((tmp << 21) | (tmp >> 11));
+               tmp = wa + (wc ^ (wb | ~wd)) + x8 + 0x6FA87E4F;
+               wa = wb + ((tmp << 6) | (tmp >> 26));
+               tmp = wd + (wb ^ (wa | ~wc)) + xF + 0xFE2CE6E0;
+               wd = wa + ((tmp << 10) | (tmp >> 22));
+               tmp = wc + (wa ^ (wd | ~wb)) + x6 + 0xA3014314;
+               wc = wd + ((tmp << 15) | (tmp >> 17));
+               tmp = wb + (wd ^ (wc | ~wa)) + xD + 0x4E0811A1;
+               wb = wc + ((tmp << 21) | (tmp >> 11));
+               tmp = wa + (wc ^ (wb | ~wd)) + x4 + 0xF7537E82;
+               wa = wb + ((tmp << 6) | (tmp >> 26));
+               tmp = wd + (wb ^ (wa | ~wc)) + xB + 0xBD3AF235;
+               wd = wa + ((tmp << 10) | (tmp >> 22));
+               tmp = wc + (wa ^ (wd | ~wb)) + x2 + 0x2AD7D2BB;
+               wc = wd + ((tmp << 15) | (tmp >> 17));
+               tmp = wb + (wd ^ (wc | ~wa)) + x9 + 0xEB86D391;
+               wb = wc + ((tmp << 21) | (tmp >> 11));
+
+               /*
+                * Update state words and reset block pointer.
+                */
+               A += wa;
+               B += wb;
+               C += wc;
+               D += wd;
+               ptr = 0;
+       }
+
+       static uint Dec32le(byte[] buf, int off)
+       {
+               return (uint)buf[off]
+                       | ((uint)buf[off + 1] << 8)
+                       | ((uint)buf[off + 2] << 16)
+                       | ((uint)buf[off + 3] << 24);
+       }
+
+       static void Enc32le(uint x, byte[] buf, int off)
+       {
+               buf[off] = (byte)x;
+               buf[off + 1] = (byte)(x >> 8);
+               buf[off + 2] = (byte)(x >> 16);
+               buf[off + 3] = (byte)(x >> 24);
+       }
+}
+
+}
diff --git a/Crypto/ModInt.cs b/Crypto/ModInt.cs
new file mode 100644 (file)
index 0000000..2aa952a
--- /dev/null
@@ -0,0 +1,1122 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Mutable container for a modular integer.
+ *
+ * Rules:
+ *
+ *   - Each instance is initialised over a given modulus, or by
+ *     duplicating an existing instance.
+ *
+ *   - All operands for a given operation must share the same modulus,
+ *     i.e. the instances must have been created with Dup() calls from
+ *     a common ancestor.
+ *
+ *   - Modulus must be odd and greater than 1.
+ */
+
+public class ModInt {
+
+       /*
+        * Dedicated class for a modulus. It maintains some useful
+        * values that depend only on the modulus.
+        */
+       class ZMod {
+
+               /*
+                * val = modulus value, 31 bits per word, little-endian
+                * bitLen = modulus bit length (exact)
+                * n0i is such that n0i * val[0] == -1 mod 2^31
+                * R = 2^(31*val.Length) (modular)
+                * R2 = 2^(31*(val.Length*2)) (modular)
+                * Rx[i] = 2^(31*(val.Length+(2^i))) (modular)
+                */
+               internal uint[] val;
+               internal int bitLen;
+               internal uint n0i;
+               internal uint[] R;
+               internal uint[] R2;
+               internal uint[][] Rx;
+               internal byte[] vm2;
+
+               internal ZMod(byte[] bmod, int off, int len)
+               {
+                       bitLen = BigInt.BitLength(bmod, off, len);
+                       if (bitLen <= 1 || (bmod[off + len - 1] & 1) == 0) {
+                               throw new CryptoException("invalid modulus");
+                       }
+                       int n = (bitLen + 30) / 31;
+                       val = new uint[n];
+                       DecodeBE(bmod, off, len, val);
+                       uint x = val[0];
+                       uint y = 2 - x;
+                       y *= 2 - y * x;
+                       y *= 2 - y * x;
+                       y *= 2 - y * x;
+                       y *= 2 - y * x;
+                       n0i = (1 + ~y) & 0x7FFFFFFF;
+
+                       /*
+                        * Compute the Rx[] values.
+                        */
+                       int zk = 0;
+                       while ((n >> zk) != 0) {
+                               zk ++;
+                       }
+                       R = new uint[n];
+                       R[n - 1] = 1;
+                       for (int i = 0; i < 31; i ++) {
+                               ModMul2(R, val);
+                       }
+                       Rx = new uint[zk][];
+                       Rx[0] = new uint[n];
+                       Array.Copy(R, 0, Rx[0], 0, n);
+                       for (int i = 0; i < 31; i ++) {
+                               ModMul2(Rx[0], val);
+                       }
+                       for (int k = 1; k < zk; k ++) {
+                               Rx[k] = new uint[n];
+                               MontyMul(Rx[k - 1], Rx[k - 1], Rx[k], val, n0i);
+                       }
+
+                       /*
+                        * Compute R2 by multiplying the relevant Rx[]
+                        * values.
+                        */
+                       R2 = null;
+                       uint[] tt = new uint[n];
+                       for (int k = 0; k < zk; k ++) {
+                               if (((n >> k) & 1) == 0) {
+                                       continue;
+                               }
+                               if (R2 == null) {
+                                       R2 = new uint[n];
+                                       Array.Copy(Rx[k], 0, R2, 0, n);
+                               } else {
+                                       MontyMul(Rx[k], R2, tt, val, n0i);
+                                       Array.Copy(tt, 0, R2, 0, n);
+                               }
+                       }
+
+                       /*
+                        * Compute N-2; used as an exponent for modular
+                        * inversion. Since modulus N is odd and greater
+                        * than 1, N-2 is necessarily positive.
+                        */
+                       vm2 = new byte[(bitLen + 7) >> 3];
+                       int cc = 2;
+                       for (int i = 0; i < vm2.Length; i ++) {
+                               int b = bmod[off + len - 1 - i];
+                               b -= cc;
+                               vm2[vm2.Length - 1 - i] = (byte)b;
+                               cc = (b >> 8) & 1;
+                       }
+               }
+       }
+
+       ZMod mod;
+       uint[] val;
+       uint[] tmp1, tmp2;
+
+       /*
+        * Get the modulus bit length.
+        */
+       public int ModBitLength {
+               get {
+                       return mod.bitLen;
+               }
+       }
+
+       /*
+        * Test whether this value is zero.
+        */
+       public bool IsZero {
+               get {
+                       return IsZeroCT != 0;
+               }
+       }
+
+       public uint IsZeroCT {
+               get {
+                       int z = (int)val[0];
+                       for (int i = 1; i < val.Length; i ++) {
+                               z |= (int)val[i];
+                       }
+                       return ~(uint)((z | -z) >> 31);
+               }
+       }
+
+       /*
+        * Test whether this value is one.
+        */
+       public bool IsOne {
+               get {
+                       return IsOneCT != 0;
+               }
+       }
+
+       public uint IsOneCT {
+               get {
+                       int z = (int)val[0] ^ 1;
+                       for (int i = 1; i < val.Length; i ++) {
+                               z |= (int)val[i];
+                       }
+                       return ~(uint)((z | -z) >> 31);
+               }
+       }
+
+       ModInt(ZMod m)
+       {
+               Init(m);
+       }
+
+       /*
+        * Create a new instance by decoding the provided modulus
+        * (unsigned big-endian). Value is zero.
+        */
+       public ModInt(byte[] modulus)
+               : this(modulus, 0, modulus.Length)
+       {
+       }
+
+       /*
+        * Create a new instance by decoding the provided modulus
+        * (unsigned big-endian). Value is zero.
+        */
+       public ModInt(byte[] modulus, int off, int len)
+       {
+               Init(new ZMod(modulus, off, len));
+       }
+
+       void Init(ZMod mod)
+       {
+               this.mod = mod;
+               int n = mod.val.Length;
+               val = new uint[n];
+               tmp1 = new uint[n];
+               tmp2 = new uint[n];
+       }
+
+       /*
+        * Duplicate this instance. The new instance uses the same
+        * modulus, and its value is initialized to the same value as
+        * this instance.
+        */
+       public ModInt Dup()
+       {
+               ModInt m = new ModInt(mod);
+               Array.Copy(val, 0, m.val, 0, val.Length);
+               return m;
+       }
+
+       /*
+        * Set the value in this instance to a copy of the value in
+        * the provided instance. The 'm' instance may use a different
+        * modulus, in which case the value may incur modular reduction.
+        */
+       public void Set(ModInt m)
+       {
+               /*
+                * If the instances use the same modulus array, then
+                * the value can simply be copied as is.
+                */
+               if (mod == m.mod) {
+                       Array.Copy(m.val, 0, val, 0, val.Length);
+                       return;
+               }
+               Reduce(m.val);
+       }
+
+       /*
+        * Set the value in this instance to a copy of the value in
+        * the provided instance, but do so only conditionally. If
+        * 'ctl' is -1, then the copy is done. If 'ctl' is 0, then the
+        * copy is NOT done. This instance and the source instance
+        * MUST use the same modulus.
+        */
+       public void CondCopy(ModInt m, uint ctl)
+       {
+               if (mod != m.mod) {
+                       throw new CryptoException("Not same modulus");
+               }
+               for (int i = 0; i < val.Length; i ++) {
+                       uint w = val[i];
+                       val[i] = w ^ (ctl & (m.val[i] ^ w));
+               }
+       }
+
+       /*
+        * Set the value in this instance to a copy of either 'a'
+        * (if ctl is -1) or 'b' (if ctl is 0).
+        */
+       public void CopyMux(uint ctl, ModInt a, ModInt b)
+       {
+               if (mod != a.mod || mod != b.mod) {
+                       throw new CryptoException("Not same modulus");
+               }
+               for (int i = 0; i < val.Length; i ++) {
+                       uint aw = a.val[i];
+                       uint bw = b.val[i];
+                       val[i] = bw ^ (ctl & (aw ^ bw));
+               }
+       }
+
+       /*
+        * Set the value in this instance to the provided integer value
+        * (which MUST be nonnegative).
+        */
+       public void Set(int x)
+       {
+               val[0] = (uint)x;
+               for (int i = 1; i < val.Length; i ++) {
+                       val[i] = 0;
+               }
+       }
+
+       /*
+        * Set this value to either 0 (if ctl is 0) or 1 (if ctl is -1),
+        * in Montgomery representation.
+        */
+       public void SetMonty(uint ctl)
+       {
+               for (int i = 0; i < val.Length; i ++) {
+                       val[i] = ctl & mod.R[i];
+               }
+       }
+
+       /*
+        * Set this value by decoding the provided integer (big-endian
+        * unsigned encoding). If the source value does not fit (it is
+        * not lower than the modulus), then this method return 0, and
+        * this instance is set to 0. Otherwise, it returns -1.
+        */
+       public uint Decode(byte[] buf)
+       {
+               return Decode(buf, 0, buf.Length);
+       }
+
+       /*
+        * Set this value by decoding the provided integer (big-endian
+        * unsigned encoding). If the source value does not fit (it is
+        * not lower than the modulus), then this method return 0, and
+        * this instance is set to 0. Otherwise, it returns -1.
+        */
+       public uint Decode(byte[] buf, int off, int len)
+       {
+               /*
+                * Decode into val[]; if the truncation loses some
+                * non-zero bytes, then this returns 0.
+                */
+               uint x = DecodeBE(buf, off, len, val);
+
+               /*
+                * Compare with modulus. We want to get a 0 if the
+                * subtraction would not yield a carry, i.e. the
+                * source value is not lower than the modulus.
+                */
+               x &= Sub(val, mod.val, 0);
+
+               /*
+                * At that point, x is -1 if the value fits, 0
+                * otherwise.
+                */
+               for (int i = 0; i < val.Length; i ++) {
+                       val[i] &= x;
+               }
+               return x;
+       }
+
+       /*
+        * Set this value by decoding the provided integer (big-endian
+        * unsigned encoding). The source value is reduced if necessary.
+        */
+       public void DecodeReduce(byte[] buf)
+       {
+               DecodeReduce(buf, 0, buf.Length);
+       }
+
+       /*
+        * Set this value by decoding the provided integer (big-endian
+        * unsigned encoding). The source value is reduced if necessary.
+        */
+       public void DecodeReduce(byte[] buf, int off, int len)
+       {
+               uint[] x = new uint[((len << 3) + 30) / 31];
+               DecodeBE(buf, off, len, x);
+               Reduce(x);
+       }
+
+       /*
+        * Set the value from the provided source array, with modular
+        * reduction.
+        */
+       void Reduce(uint[] b)
+       {
+               /*
+                * If the modulus uses only one word then we must use
+                * a special code path.
+                */
+               if (mod.val.Length == 1) {
+                       ReduceSmallMod(b);
+                       return;
+               }
+
+               /*
+                * Fast copy of words that do not incur modular
+                * reduction.
+                */
+               int aLen = mod.val.Length;
+               int bLen = b.Length;
+               int cLen = Math.Min(aLen - 1, bLen);
+               Array.Copy(b, bLen - cLen, val, 0, cLen);
+               for (int i = cLen; i < aLen; i ++) {
+                       val[i] = 0;
+               }
+
+               /*
+                * Inject extra words. We use the pre-computed
+                * Rx[] values to do shifts.
+                */
+               for (int j = bLen - cLen; j > 0;) {
+                       /*
+                        * We can add power-of-2 words, but less
+                        * than the modulus size. Note that the modulus
+                        * uses at least two words, so this process works.
+                        */
+                       int k;
+                       for (k = 0;; k ++) {
+                               int nk = 1 << (k + 1);
+                               if (nk >= aLen || nk > j) {
+                                       break;
+                               }
+                       }
+                       int num = 1 << k;
+                       MontyMul(val, mod.Rx[k], tmp1, mod.val, mod.n0i);
+                       j -= num;
+                       Array.Copy(b, j, tmp2, 0, num);
+                       for (int i = num; i < tmp2.Length; i ++) {
+                               tmp2[i] = 0;
+                       }
+                       ModAdd(tmp1, tmp2, val, mod.val, 0);
+               }
+       }
+
+       /*
+        * Modular reduction in case the modulus fits on a single
+        * word.
+        */
+       void ReduceSmallMod(uint[] b)
+       {
+               uint x = 0;
+               uint n = mod.val[0];
+               int nlen = mod.bitLen;
+               uint n0i = mod.n0i;
+               uint r2 = mod.R2[0];
+               for (int i = b.Length - 1; i >= 0; i --) {
+                       /*
+                        * Multiply x by R (Montgomery multiplication by R^2).
+                        */
+                       ulong z = (ulong)x * (ulong)r2;
+                       uint u = ((uint)z * n0i) & 0x7FFFFFFF;
+                       z += (ulong)u * (ulong)n;
+                       x = (uint)(z >> 31);
+
+                       /*
+                        * Ensure x fits on 31 bits (it may be up to twice
+                        * the modulus at that point). If x >= 2^31 then,
+                        * necessarily, x is greater than the modulus, and
+                        * the subtraction is sound; moreover, in that case,
+                        * subtracting the modulus brings back x to less
+                        * than the modulus, hence fitting on 31 bits.
+                        */
+                       x -= (uint)((int)x >> 31) & n;
+
+                       /*
+                        * Add the next word, then reduce. The addition
+                        * does not overflow since both operands fit on
+                        * 31 bits.
+                        *
+                        * Since the modulus could be much smaller than
+                        * 31 bits, we need a full remainder operation here.
+                        */
+                       x += b[i];
+
+                       /*
+                        * Constant-time modular reduction.
+                        * We first perform two subtraction of the
+                        * shifted modulus to ensure that the high bit
+                        * is cleared. This allows the loop to work
+                        * properly.
+                        */
+                       x -= (uint)((int)x >> 31) & (n << (31 - nlen));
+                       x -= (uint)((int)x >> 31) & (n << (31 - nlen));
+                       for (int j = 31 - nlen; j >= 0; j --) {
+                               x -= (n << j);
+                               x += (uint)((int)x >> 31) & (n << j);
+                       }
+               }
+               val[0] = x;
+       }
+
+       /*
+        * Encode into bytes. Big-endian unsigned encoding is used, the
+        * returned array having the minimal length to encode the modulus.
+        */
+       public byte[] Encode()
+       {
+               return Encode(false);
+       }
+
+       /*
+        * Encode into bytes. Big-endian encoding is used; if 'signed' is
+        * true, then signed encoding is used: returned value will have a
+        * leading bit set to 0. Returned array length is the minimal size
+        * for encoding the modulus (with a sign bit if using signed
+        * encoding).
+        */
+       public byte[] Encode(bool signed)
+       {
+               int x = mod.bitLen;
+               if (signed) {
+                       x ++;
+               }
+               byte[] buf = new byte[(x + 7) >> 3];
+               Encode(buf, 0, buf.Length);
+               return buf;
+       }
+
+       /*
+        * Encode into bytes. The provided array is fully set; big-endian
+        * encoding is used, and extra leading bytes of value 0 are added
+        * if necessary. If the destination array is too small, then the
+        * value is silently truncated.
+        */
+       public void Encode(byte[] buf)
+       {
+               Encode(buf, 0, buf.Length);
+       }
+
+       /*
+        * Encode into bytes. The provided array chunk is fully set;
+        * big-endian encoding is used, and extra leading bytes of value
+        * 0 are added if necessary. If the destination array is too
+        * small, then the value is silently truncated.
+        */
+       public void Encode(byte[] buf, int off, int len)
+       {
+               EncodeBE(val, buf, off, len);
+       }
+
+       /*
+        * Get the least significant bit of the value (0 or 1).
+        */
+       public uint GetLSB()
+       {
+               return val[0] & (uint)1;
+       }
+
+       /*
+        * Add a small integer to this instance. The small integer
+        * 'x' MUST be lower than 2^31 and MUST be lower than the modulus.
+        */
+       public void Add(uint x)
+       {
+               tmp1[0] = x;
+               for (int i = 1; i < tmp1.Length; i ++) {
+                       tmp1[i] = 0;
+               }
+               ModAdd(val, tmp1, val, mod.val, 0);
+       }
+
+       /*
+        * Add another value to this instance. The operand 'b' may
+        * be the same as this instance.
+        */
+       public void Add(ModInt b)
+       {
+               if (mod != b.mod) {
+                       throw new CryptoException("Not same modulus");
+               }
+               ModAdd(val, b.val, val, mod.val, 0);
+       }
+
+       /*
+        * Subtract a small integer from this instance. The small integer
+        * 'x' MUST be lower than 2^31 and MUST be lower than the modulus.
+        */
+       public void Sub(uint x)
+       {
+               tmp1[0] = x;
+               for (int i = 1; i < tmp1.Length; i ++) {
+                       tmp1[i] = 0;
+               }
+               ModSub(val, tmp1, val, mod.val);
+       }
+
+       /*
+        * Subtract another value from this instance. The operand 'b'
+        * may be the same as this instance.
+        */
+       public void Sub(ModInt b)
+       {
+               if (mod != b.mod) {
+                       throw new CryptoException("Not same modulus");
+               }
+               ModSub(val, b.val, val, mod.val);
+       }
+
+       /*
+        * Negate this value.
+        */
+       public void Negate()
+       {
+               ModSub(null, val, val, mod.val);
+       }
+
+       /*
+        * Convert this instance to Montgomery representation.
+        */
+       public void ToMonty()
+       {
+               MontyMul(val, mod.R2, tmp1, mod.val, mod.n0i);
+               Array.Copy(tmp1, 0, val, 0, val.Length);
+       }
+
+       /*
+        * Convert this instance back from Montgomery representation to
+        * normal representation.
+        */
+       public void FromMonty()
+       {
+               tmp1[0] = 1;
+               for (int i = 1; i < tmp1.Length; i ++) {
+                       tmp1[i] = 0;
+               }
+               MontyMul(val, tmp1, tmp2, mod.val, mod.n0i);
+               Array.Copy(tmp2, 0, val, 0, val.Length);
+       }
+
+       /*
+        * Compute a Montgomery multiplication with the provided
+        * value. The other operand may be this instance.
+        */
+       public void MontyMul(ModInt b)
+       {
+               if (mod != b.mod) {
+                       throw new CryptoException("Not same modulus");
+               }
+               MontyMul(val, b.val, tmp1, mod.val, mod.n0i);
+               Array.Copy(tmp1, 0, val, 0, val.Length);
+       }
+
+       /*
+        * Montgomery-square this instance.
+        */
+       public void MontySquare()
+       {
+               MontyMul(val, val, tmp1, mod.val, mod.n0i);
+               Array.Copy(tmp1, 0, val, 0, val.Length);
+       }
+
+       /*
+        * Perform modular exponentiation. Exponent is in big-endian
+        * unsigned encoding.
+        */
+       public void Pow(byte[] exp)
+       {
+               Pow(exp, 0, exp.Length);
+       }
+
+       /*
+        * Perform modular exponentiation. Exponent is in big-endian
+        * unsigned encoding.
+        */
+       public void Pow(byte[] exp, int off, int len)
+       {
+               MontyMul(val, mod.R2, tmp1, mod.val, mod.n0i);
+               val[0] = 1;
+               for (int i = 1; i < val.Length; i ++) {
+                       val[i] = 0;
+               }
+               for (int i = 0; i < len; i ++) {
+                       int x = exp[off + len - 1 - i];
+                       for (int j = 0; j < 8; j ++) {
+                               MontyMul(val, tmp1, tmp2, mod.val, mod.n0i);
+                               uint ctl = (uint)-((x >> j) & 1);
+                               for (int k = 0; k < val.Length; k ++) {
+                                       val[k] = (tmp2[k] & ctl)
+                                               | (val[k] & ~ctl);
+                               }
+                               MontyMul(tmp1, tmp1, tmp2, mod.val, mod.n0i);
+                               Array.Copy(tmp2, 0, tmp1, 0, tmp2.Length);
+                       }
+               }
+       }
+
+       /*
+        * Compute modular inverse of this value. If this instance is
+        * zero, then it remains equal to zero. If the modulus is not
+        * prime, then this function computes wrong values.
+        */
+       public void Invert()
+       {
+               Pow(mod.vm2);
+       }
+
+       /*
+        * Compute the square root for this value. This method assumes
+        * that the modulus is prime, greater than or equal to 7, and
+        * equal to 3 modulo 4; if it is not, then the returned value
+        * and the contents of this instance are indeterminate.
+        *
+        * Returned value is -1 if the value was indeed a square, 0
+        * otherwise. In the latter case, array contents are the square
+        * root of the opposite of the original value.
+        */
+       public uint SqrtBlum()
+       {
+               /*
+                * We suppose that p = 3 mod 4; we raise to the power
+                * (p-3)/4, then do an extra multiplication to go to
+                * power (p+1)/4.
+                *
+                * Since we know the modulus bit length, we can do
+                * the exponentiation from the high bits downwards.
+                */
+               ToMonty();
+               Array.Copy(val, 0, tmp1, 0, val.Length);
+               int k = (mod.bitLen - 2) / 31;
+               int j = mod.bitLen - 2 - k * 31;
+               uint ew = mod.val[k];
+               for (int i = mod.bitLen - 2; i >= 2; i --) {
+                       uint ctl = ~(uint)-((int)(ew >> j) & 1);
+                       MontyMul(tmp1, tmp1, tmp2, mod.val, mod.n0i);
+                       MontyMul(val, tmp2, tmp1, mod.val, mod.n0i);
+                       for (int m = 0; m < tmp1.Length; m ++) {
+                               uint w = tmp1[m];
+                               tmp1[m] = w ^ (ctl & (w ^ tmp2[m]));
+                       }
+                       if (-- j < 0) {
+                               j = 30;
+                               ew = mod.val[-- k];
+                       }
+               }
+
+               /*
+                * The extra multiplication. Square root is written in
+                * tmp2 (in Montgomery representation).
+                */
+               MontyMul(val, tmp1, tmp2, mod.val, mod.n0i);
+
+               /*
+                * Square it back in tmp1, to see if it indeed yields
+                * val.
+                */
+               MontyMul(tmp2, tmp2, tmp1, mod.val, mod.n0i);
+               int z = 0;
+               for (int i = 0; i < val.Length; i ++) {
+                       z |= (int)(val[i] ^ tmp1[i]);
+               }
+               uint good = ~(uint)((z | -z) >> 31);
+
+               /*
+                * Convert back the result to normal representation.
+                */
+               Array.Copy(tmp2, 0, val, 0, val.Length);
+               FromMonty();
+               return good;
+       }
+
+       /*
+        * Conditionally swap this instance with the one provided in
+        * parameter. The swap is performed if ctl is -1, not performed
+        * if ctl is 0.
+        */
+       public void CondSwap(ModInt b, uint ctl)
+       {
+               if (mod != b.mod) {
+                       throw new CryptoException("Not same modulus");
+               }
+               for (int i = 0; i < val.Length; i ++) {
+                       uint x = val[i];
+                       uint y = b.val[i];
+                       uint m = ctl & (x ^ y);
+                       val[i] = x ^ m;
+                       b.val[i] = y ^ m;
+               }
+       }
+
+       /*
+        * Compare for equality this value with another. Comparison still
+        * works if the two values use distinct moduli.
+        */
+       public bool Eq(ModInt b)
+       {
+               return EqCT(b) != 0;
+       }
+
+       /*
+        * Compare for equality this value with another. Comparison still
+        * works if the two values use distinct moduli. Returned value is
+        * -1 on equality, 0 otherwise.
+        */
+       public uint EqCT(ModInt b)
+       {
+               uint z = 0;
+               if (b.val.Length > val.Length) {
+                       for (int i = 0; i < val.Length; i ++) {
+                               z |= val[i] ^ b.val[i];
+                       }
+                       for (int i = val.Length; i < b.val.Length; i ++) {
+                               z |= b.val[i];
+                       }
+               } else {
+                       for (int i = 0; i < b.val.Length; i ++) {
+                               z |= val[i] ^ b.val[i];
+                       }
+                       for (int i = b.val.Length; i < val.Length; i ++) {
+                               z |= b.val[i];
+                       }
+               }
+               int x = (int)z;
+               return ~(uint)((x | -x) >> 31);
+       }
+
+       /* ============================================================== */
+
+       /*
+        * Decode value (unsigned big-endian) into dst[] (little-endian,
+        * 31 bits per word). All words of dst[] are initialised.
+        * Returned value is -1 on success, 0 if some non-zero source
+        * bits had to be ignored.
+        */
+       static uint DecodeBE(byte[] buf, int off, int len, uint[] dst)
+       {
+               int i = 0;
+               uint acc = 0;
+               int accLen = 0;
+               off += len;
+               while (i < dst.Length) {
+                       uint b;
+                       if (len > 0) {
+                               b = buf[-- off];
+                               len --;
+                       } else {
+                               b = 0;
+                       }
+                       acc |= (b << accLen);
+                       accLen += 8;
+                       if (accLen >= 31) {
+                               dst[i ++] = acc & 0x7FFFFFFF;
+                               accLen -= 31;
+                               acc = b >> (8 - accLen);
+                       }
+               }
+               while (len -- > 0) {
+                       acc |= buf[-- off];
+               }
+               int x = (int)acc;
+               return ~(uint)((x | -x) >> 31);
+       }
+
+       /*
+        * Encode an integer (array of words, little-endian, 31 bits per
+        * word) into big-endian encoding, with the provided length (in
+        * bytes).
+        */
+       static byte[] EncodeBE(uint[] x, int len)
+       {
+               byte[] val = new byte[len];
+               EncodeBE(x, val, 0, len);
+               return val;
+       }
+
+       /*
+        * Encode an integer (array of words, little-endian, 31 bits per
+        * word) into big-endian encoding, with the provided length (in
+        * bytes).
+        */
+       static void EncodeBE(uint[] x, byte[] val)
+       {
+               EncodeBE(x, val, 0, val.Length);
+       }
+
+       /*
+        * Encode an integer (array of words, little-endian, 31 bits per
+        * word) into big-endian encoding, with the provided length (in
+        * bytes).
+        */
+       static void EncodeBE(uint[] x, byte[] val, int off, int len)
+       {
+               uint acc = 0;
+               int accLen = 0;
+               int j = 0;
+               for (int i = len - 1; i >= 0; i --) {
+                       uint b;
+                       if (accLen < 8) {
+                               uint z = (j < x.Length) ? x[j ++] : 0;
+                               b = acc | (z << accLen);
+                               acc = z >> (8 - accLen);
+                               accLen += 23;
+                       } else {
+                               b = acc;
+                               accLen -= 8;
+                               acc >>= 8;
+                       }
+                       val[off + i] = (byte)b;
+               }
+       }
+
+       /*
+        * Subtract b from a; the carry is returned (-1 if carry, 0
+        * otherwise). The operation is done only if ctl is -1; if
+        * ctl is 0, then a[] is unmodified, but the carry is still
+        * computed and returned.
+        *
+        * The two operand arrays MUST have the same size.
+        */
+       static uint Sub(uint[] a, uint[] b, uint ctl)
+       {
+               int n = a.Length;
+               int cc = 0;
+               ctl >>= 1;
+               for (int i = 0; i < n; i ++) {
+                       uint aw = a[i];
+                       uint bw = b[i];
+                       uint cw = (uint)cc + aw - bw;
+                       cc = (int)cw >> 31;
+                       a[i] = (aw & ~ctl) | (cw & ctl);
+               }
+               return (uint)cc;
+       }
+
+       /*
+        * Left-shift value by one bit; the extra bit (carry) is
+        * return as -1 or 0.
+        */
+       static uint LShift(uint[] a)
+       {
+               int n = a.Length;
+               uint cc = 0;
+               for (int i = 0; i < n; i ++) {
+                       uint aw = a[i];
+                       a[i] = (cc | (aw << 1)) & 0x7FFFFFFF;
+                       cc = aw >> 30;
+               }
+               return (uint)-(int)cc;
+       }
+
+       /*
+        * Modular left-shift value by one bit. Value and modulus MUST
+        * have the same length. Value MUST be lower than modulus.
+        */
+       static void ModMul2(uint[] a, uint[] mod)
+       {
+               int n = a.Length;
+
+               /*
+                * First pass: compute 2*a-mod, but don't keep the
+                * result, only the final carry (0 or -1).
+                */
+               uint cc1 = 0;
+               int cc2 = 0;
+               for (int i = 0; i < n; i ++) {
+                       uint aw = a[i];
+                       uint aws = ((aw << 1) | cc1) & 0x7FFFFFFF;
+                       cc1 = aw >> 30;
+                       uint z = aws - mod[i] + (uint)cc2;
+                       cc2 = (int)z >> 31;
+               }
+               cc2 += (int)cc1;
+
+               /*
+                * If cc2 is 0, then the subtraction yields no carry and
+                * must be done. Otherwise, cc2 is -1, and the subtraction
+                * must not be done.
+                */
+               uint ctl = ~(uint)cc2;
+               cc1 = 0;
+               cc2 = 0;
+               for (int i = 0; i < n; i ++) {
+                       uint aw = a[i];
+                       uint aws = ((aw << 1) | cc1) & 0x7FFFFFFF;
+                       cc1 = aw >> 30;
+                       uint z = aws - (mod[i] & ctl) + (uint)cc2;
+                       cc2 = (int)z >> 31;
+                       a[i] = z & 0x7FFFFFFF;
+               }
+       }
+
+       /*
+        * Modular addition.
+        *
+        * If 'hi' is zero, then this computes a+b-n if a+b >= n,
+        * a+b otherwise.
+        *
+        * If 'hi' is non-zero, then this computes 2^k+a+b-n, where
+        * k = n.Length*31.
+        *
+        * Result is written in d[]. The same array may be used in
+        * several operands.
+        */
+       static void ModAdd(uint[] a, uint[] b, uint[] d, uint[] n, uint hi)
+       {
+               /*
+                * Set ctl to -1 if hi is non-zero, 0 otherwise.
+                * 'ctl' computes whether the subtraction with n[]
+                * is needed in the second pass.
+                */
+               int x = (int)hi;
+               uint ctl = (uint)((x | -x) >> 31);
+
+               for (int pass = 0; pass < 2; pass ++) {
+                       /*
+                        * cc1 is the carry for a+b (0 or 1)
+                        *
+                        * cc2 is the carry for the modulus
+                        * subtraction (0 or -1)
+                        */
+                       uint cc1 = 0;
+                       int cc2 = 0;
+                       for (int i = 0; i < n.Length; i ++) {
+                               uint aw = a[i];
+                               uint bw = b[i];
+                               uint nw = n[i];
+                               uint sw = aw + bw + cc1;
+                               cc1 = sw >> 31;
+                               sw &= 0x7FFFFFFF;
+                               uint dw = sw - nw + (uint)cc2;
+                               cc2 = (int)dw >> 31;
+                               if (pass == 1) {
+                                       dw &= 0x7FFFFFFF;
+                                       d[i] = (dw & ctl) | (sw & ~ctl);
+                               }
+                       }
+
+                       /*
+                        * Compute aggregate subtraction carry. This should
+                        * not be 1 if the operands are correct (it would
+                        * mean that a+b-n overflows, so both a and b are
+                        * larger than n). If it is 0, then a+b-n >= 0,
+                        * so the subtraction must be done; otherwise, the
+                        * aggregate carry will be -1, and the subtraction
+                        * should not be done (unless forced by a non-zero
+                        * 'hi' value).
+                        */
+                       cc2 += (int)cc1;
+                       ctl |= ~(uint)(cc2 >> 31);
+               }
+       }
+
+       /*
+        * Modular subtraction.
+        *
+        * This computes a-b, then adds n if the result is negative.
+        * If a is null, then it is assumed to be all-zeros.
+        *
+        * Result is written in d[]. The same array may be used in
+        * several operands.
+        */
+       static void ModSub(uint[] a, uint[] b, uint[] d, uint[] n)
+       {
+               uint ctl = 0;
+               for (int pass = 0; pass < 2; pass ++) {
+                       /*
+                        * cc1 = carry for a-b (0 or -1)
+                        * cc2 = carry for modulus addition (0 or 1)
+                        */
+                       int cc1 = 0;
+                       uint cc2 = 0;
+                       for (int i = 0; i < n.Length; i ++) {
+                               uint aw = (a == null) ? 0 : a[i];
+                               uint bw = b[i];
+                               uint nw = n[i];
+                               uint sw = aw - bw + (uint)cc1;
+                               cc1 = (int)sw >> 31;
+                               sw &= 0x7FFFFFFF;
+                               uint dw = sw + nw + cc2;
+                               cc2 = dw >> 31;
+                               if (pass == 1) {
+                                       dw &= 0x7FFFFFFF;
+                                       d[i] = (dw & ctl) | (sw & ~ctl);
+                               }
+                       }
+
+                       /*
+                        * Modulus addition must be done if and only if
+                        * a-b had a final carry.
+                        */
+                       ctl = (uint)cc1;
+               }
+       }
+
+       /*
+        * Compute Montgomery multiplication of a[] by b[], result in
+        * d[], with modulus n[]. All arrays must have the same length.
+        * d[] must be distinct from a[], b[] and n[]. Modulus must be
+        * odd. n0i must be such that n[0]*n0i = -1 mod 2^31. Values a[]
+        * and b[] must be lower than n[] (so, in particular, a[] and
+        * b[] cannot be the same array as n[]).
+        */
+       static void MontyMul(uint[] a, uint[] b, uint[] d, uint[] n, uint n0i)
+       {
+               int len = n.Length;
+               for (int i = 0; i < len; i ++) {
+                       d[i] = 0;
+               }
+               ulong dh = 0;
+               for (int i = 0; i < len; i ++) {
+                       uint ai = a[i];
+                       uint u = ((d[0] + ai * b[0]) * n0i) & 0x7FFFFFFF;
+                       ulong cc = 0;
+                       for (int j = 0; j < len; j ++) {
+                               ulong z = (ulong)d[j]
+                                       + (ulong)ai * (ulong)b[j]
+                                       + (ulong)u * (ulong)n[j] + cc;
+                               cc = z >> 31;
+                               if (j > 0) {
+                                       d[j - 1] = (uint)z & 0x7FFFFFFF;
+                               } else {
+                                       // DEBUG
+                                       if (((uint)z & 0x7FFFFFFF) != 0) {
+                                               throw new Exception("BAD!");
+                                       }
+                               }
+                       }
+                       dh += cc;
+                       d[len - 1] = (uint)dh & 0x7FFFFFFF;
+                       dh >>= 31;
+               }
+               int x = (int)dh;
+               uint ctl = (uint)((x | -x) >> 31);
+               Sub(d, n, ctl | ~Sub(d, n, 0));
+       }
+}
+
+}
diff --git a/Crypto/MutableECPoint.cs b/Crypto/MutableECPoint.cs
new file mode 100644 (file)
index 0000000..ede06df
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * A MutableECPoint instance contains an elliptic curve point, in a
+ * given curve. It may be modified to contain another point, but not
+ * on another curve.
+ *
+ * Constant-time guarantees: IsInfinityCT, DoubleCT(), NegCT() and
+ * AddCT() are constant-time with regards to the represented curve
+ * point. Execution time may vary depending on the sequence of calls,
+ * but not on the point data. In particular, points may be internally
+ * "normalized" or not, and operations involving normalized points can
+ * be faster; however, normalization happens only upon an explicit call.
+ * The normalization process itself (Normalize()) is constant-time.
+ */
+
+internal abstract class MutableECPoint {
+
+       internal MutableECPoint()
+       {
+       }
+
+       internal abstract ECCurve Curve {
+               get;
+       }
+
+       /*
+        * Test whether this point is the point at infinity.
+        */
+       internal bool IsInfinity {
+               get {
+                       return IsInfinityCT != 0;
+               }
+       }
+
+       /*
+        * Test whether this point is the point at infinity (returns
+        * 0xFFFFFFFF or 0x00000000).
+        */
+       internal abstract uint IsInfinityCT {
+               get;
+       }
+
+       /*
+        * Normalize this instance. What this entails depends on the
+        * curve type, but it will typically means computing affine
+        * coordinates in case this instance was using some sort of
+        * projective system.
+        */
+       internal abstract void Normalize();
+
+       /*
+        * Encode this point into some bytes. If "compressed" is true,
+        * then a compressed format will be used.
+        *
+        * This call may entail normalization. If the point is not the
+        * infinity point, then this method is constant-time.
+        */
+       internal abstract byte[] Encode(bool compressed);
+
+       /*
+        * Encode this point into some bytes. If "compressed is true,
+        * then a compressed format will be used. The destination array
+        * must have the proper length for the requested point format.
+        *
+        * This call may entail normalization. If the point is invalid
+        * or is the point at infinity, then the returned value is 0
+        * and what gets written in the array is indeterminate. Otherwise,
+        * the encoded point is written and -1 is returned. Either way,
+        * this call is constant-time.
+        */
+       internal abstract uint Encode(byte[] dst, bool compressed);
+
+       /*
+        * Set this point by decoding the provided value. An invalid
+        * encoding sets this point to 0 (infinity) and triggers an
+        * exception.
+        */
+       internal void Decode(byte[] enc)
+       {
+               if (DecodeCT(enc) == 0) {
+                       throw new CryptoException("Invalid encoded point");
+               }
+       }
+
+       /*
+        * Set this point by decoding the provided value. This is
+        * constant-time (up to the encoded point length). Returned
+        * value is 0xFFFFFFFF if the encoded point was valid,
+        * 0x00000000 otherwise. If the decoding failed, then this
+        * value is set to 0 (infinity).
+        */
+       internal abstract uint DecodeCT(byte[] enc);
+
+       /*
+        * Get the X coordinate for this point. This implies
+        * normalization. If the point is the point at infinity,
+        * then the returned array contains the encoding of 0.
+        * This is constant-time.
+        */
+       internal abstract byte[] X {
+               get;
+       }
+
+       /*
+        * Get the Y coordinate for this point. This implies
+        * normalization. If the point is the point at infinity,
+        * then the returned array contains the encoding of 0.
+        * This is constant-time.
+        */
+       internal abstract byte[] Y {
+               get;
+       }
+
+       /*
+        * Create a new instance that starts with the same contents as
+        * this point.
+        */
+       internal abstract MutableECPoint Dup();
+
+       /*
+        * Set this instance to the point at infinity.
+        */
+       internal abstract void SetZero();
+
+       /*
+        * Set this instance to the same contents as the provided point.
+        * The operand Q must be part of the same curve.
+        */
+       internal abstract void Set(MutableECPoint Q);
+
+       /*
+        * Set this instance to the same contents as the provided point,
+        * but only if ctl == 0xFFFFFFFFF. If ctl == 0x00000000, then
+        * this instance is unmodified. The operand Q must be part of
+        * the same curve.
+        */
+       internal abstract void Set(MutableECPoint Q, uint ctl);
+
+       /*
+        * Set this instance to the same contents as point P1 if
+        * ctl == 0xFFFFFFFF, or point P2 if ctl == 0x00000000.
+        * Both operands must use the same curve as this instance.
+        */
+       internal abstract void SetMux(uint ctl,
+               MutableECPoint P1, MutableECPoint P2);
+
+       /*
+        * DoubleCT() is constant-time. It works for all points
+        * (including points of order 2 and the infinity point).
+        */
+       internal abstract void DoubleCT();
+
+       /*
+        * AddCT() computes P+Q (P is this instance, Q is the operand).
+        * It may assume that P != Q. If P = Q and the method could not
+        * compute the correct result, then it shall set this instance to
+        * 0 (infinity) and return 0x00000000. In all other cases, it must
+        * compute the correct point and return 0xFFFFFFFF. In particular,
+        * it should properly handle cases where P = 0 or Q = 0. This
+        * function is allowed to handle doubling cases as well, if it
+        * can.
+        *
+        * This method may be more efficient if the operand is
+        * normalized. Execution time and memory access may depend on
+        * whether this instance or the other operand is normalized,
+        * but not on the actual point values (including if the points
+        * do not fulfill the properties above).
+        */
+       internal abstract uint AddCT(MutableECPoint Q);
+
+       /*
+        * Negate this point. It also works on the point at infinity,
+        * and it is constant-time.
+        */
+       internal abstract void NegCT();
+
+       /*
+        * Multiply this point by the provided integer (unsigned
+        * big-endian representation). This is constant-time. This
+        * method assumes that:
+        * -- the point on which we are operating is part of the curve
+        *    defined subgroup;
+        * -- the defined subgroup has a prime order which is no less
+        *    than 17;
+        * -- the point is not the point at infinity;
+        * -- the multiplier operand is no more than the subgroup order.
+        * If these conditions are met, then the resulting point will
+        * be the proper element of the defined subgroup (it will be
+        * the point at infinity only if the multiplier is 0 or is
+        * equal to the subgroup order). If they are NOT met, then the
+        * resulting point is undefined (but will still be part of the
+        * curve).
+        *
+        * This method is constant-time.
+        *
+        * Returned value is 0xFFFFFFFF if none of the internal
+        * operations reached a problematic state (i.e. that we tried to
+        * perform an addition and the two operands turned out to be
+        * equal to each other). If the conditions above are met, then
+        * this is always the case. If a problematic state was reached,
+        * then the returned value is 0x00000000. Callers MUST be very
+        * cautious about using that reported error state, since it is
+        * not guaranteed that all invalid points would be reported as
+        * such. There thus is potential for leakage of secret data.
+        */
+       internal abstract uint MulSpecCT(byte[] n);
+
+       /*
+        * Compare this point to another. This method throws an
+        * exception if the provided point is not on the same curve as
+        * this instance. It otherwise returns 0xFFFFFFFF if both points
+        * are equal, 0x00000000 otherwise. This method is constant-time
+        * (its execution time may depend on whether this and/or the
+        * other point is normalized or not, but not on the actual
+        * values).
+        */
+       internal abstract uint EqCT(MutableECPoint Q);
+
+       internal bool Eq(MutableECPoint Q)
+       {
+               return EqCT(Q) != 0;
+       }
+}
+
+}
diff --git a/Crypto/MutableECPointCurve25519.cs b/Crypto/MutableECPointCurve25519.cs
new file mode 100644 (file)
index 0000000..b3fdf7a
--- /dev/null
@@ -0,0 +1,286 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * An implementation of MutableECPoint for Curve25519.
+ * This is a partial implementation that only supports multiplication
+ * by a scalar, not general addition.
+ */
+
+internal class MutableECPointCurve25519 : MutableECPoint {
+
+       ModInt x;
+       ModInt x1, x2, z2, x3, z3;
+       ModInt a, aa, b, bb, c, d, e;
+       byte[] u;
+
+       /*
+        * Create a new instance. It is initialized to the point at
+        * infinity (represented with a 0).
+        */
+       internal MutableECPointCurve25519()
+       {
+               ECCurve25519 ccc = (ECCurve25519)EC.Curve25519;
+               x = ccc.mp.Dup();
+               x1 = ccc.mp.Dup();
+               x2 = ccc.mp.Dup();
+               z2 = ccc.mp.Dup();
+               x3 = ccc.mp.Dup();
+               z3 = ccc.mp.Dup();
+               a = ccc.mp.Dup();
+               aa = ccc.mp.Dup();
+               b = ccc.mp.Dup();
+               bb = ccc.mp.Dup();
+               c = ccc.mp.Dup();
+               d = ccc.mp.Dup();
+               e = ccc.mp.Dup();
+               u = new byte[32];
+       }
+
+       internal override ECCurve Curve {
+               get {
+                       return EC.Curve25519;
+               }
+       }
+
+       internal override uint IsInfinityCT {
+               get {
+                       return 0;
+               }
+       }
+
+       internal override void Normalize()
+       {
+       }
+
+       internal override byte[] Encode(bool compressed)
+       {
+               byte[] r = new byte[32];
+               Encode(r, false);
+               return r;
+       }
+
+       internal override uint Encode(byte[] dst, bool compressed)
+       {
+               if (dst.Length != 32) {
+                       throw new CryptoException("invalid output length");
+               }
+               x.Encode(u, 0, 32);
+               for (int i = 0; i < 32; i ++) {
+                       dst[i] = u[31 - i];
+               }
+               return 0xFFFFFFFF;
+       }
+
+       internal override uint DecodeCT(byte[] enc)
+       {
+               if (enc.Length != 32) {
+                       return 0;
+               }
+               for (int i = 0; i < 32; i ++) {
+                       u[i] = enc[31 - i];
+               }
+               u[0] &= 0x7F;
+               x.DecodeReduce(u);
+               return 0xFFFFFFFF;
+       }
+
+       internal override byte[] X {
+               get {
+                       return x.Encode();
+               }
+       }
+
+       internal override byte[] Y {
+               get {
+                       throw new CryptoException(
+                               "Not implemented for Curve25519");
+               }
+       }
+
+       internal override MutableECPoint Dup()
+       {
+               MutableECPointCurve25519 Q = new MutableECPointCurve25519();
+               Q.Set(this);
+               return Q;
+       }
+
+       internal void Set(byte[] X, byte[] Y, bool check)
+       {
+               throw new CryptoException("Not implemented for Curve25519");
+       }
+
+       internal void Set(ModInt X, ModInt Y, bool check)
+       {
+               throw new CryptoException("Not implemented for Curve25519");
+       }
+
+       internal override void SetZero()
+       {
+               throw new CryptoException("Not implemented for Curve25519");
+       }
+
+       internal override void Set(MutableECPoint Q)
+       {
+               MutableECPointCurve25519 R = SameCurve(Q);
+               x.Set(R.x);
+       }
+
+       internal override void Set(MutableECPoint Q, uint ctl)
+       {
+               MutableECPointCurve25519 R = SameCurve(Q);
+               x.CondCopy(R.x, ctl);
+       }
+
+       internal override void SetMux(uint ctl,
+               MutableECPoint P1, MutableECPoint P2)
+       {
+               SetMuxInner(ctl, SameCurve(P1), SameCurve(P2));
+       }
+
+       void SetMuxInner(uint ctl,
+               MutableECPointCurve25519 P1, MutableECPointCurve25519 P2)
+       {
+               x.CopyMux(ctl, P1.x, P2.x);
+       }
+
+       internal override void DoubleCT()
+       {
+               throw new CryptoException("Not implemented for Curve25519");
+       }
+
+       internal override uint AddCT(MutableECPoint Q)
+       {
+               throw new CryptoException("Not implemented for Curve25519");
+       }
+
+       internal override void NegCT()
+       {
+               throw new CryptoException("Not implemented for Curve25519");
+       }
+
+       internal override uint MulSpecCT(byte[] n)
+       {
+               /*
+                * Copy scalar into a temporary array (u[]) for
+                * normalisation to 32 bytes and clamping.
+                */
+               if (n.Length > 32) {
+                       return 0;
+               }
+               Array.Copy(n, 0, u, 32 - n.Length, n.Length);
+               for (int i = 0; i < 32 - n.Length; i ++) {
+                       u[i] = 0;
+               }
+               u[31] &= 0xF8;
+               u[0] &= 0x7F;
+               u[0] |= 0x40;
+
+               x1.Set(x);
+               x1.ToMonty();
+               x2.SetMonty(0xFFFFFFFF);
+               z2.Set(0);
+               x3.Set(x1);
+               z3.Set(x2);
+               uint swap = 0;
+               ModInt ma24 = ((ECCurve25519)EC.Curve25519).ma24;
+
+               for (int t = 254; t >= 0; t --) {
+                       uint kt = (uint)-((u[31 - (t >> 3)] >> (t & 7)) & 1);
+                       swap ^= kt;
+                       x2.CondSwap(x3, swap);
+                       z2.CondSwap(z3, swap);
+                       swap = kt;
+
+                       a.Set(x2);
+                       a.Add(z2);
+                       aa.Set(a);
+                       aa.MontySquare();
+                       b.Set(x2);
+                       b.Sub(z2);
+                       bb.Set(b);
+                       bb.MontySquare();
+                       e.Set(aa);
+                       e.Sub(bb);
+                       c.Set(x3);
+                       c.Add(z3);
+                       d.Set(x3);
+                       d.Sub(z3);
+                       d.MontyMul(a);
+                       c.MontyMul(b);
+                       x3.Set(d);
+                       x3.Add(c);
+                       x3.MontySquare();
+                       z3.Set(d);
+                       z3.Sub(c);
+                       z3.MontySquare();
+                       z3.MontyMul(x1);
+                       x2.Set(aa);
+                       x2.MontyMul(bb);
+                       z2.Set(e);
+                       z2.MontyMul(ma24);
+                       z2.Add(aa);
+                       z2.MontyMul(e);
+               }
+               x2.CondSwap(x3, swap);
+               z2.CondSwap(z3, swap);
+
+               /*
+                * We need to restore z2 to normal representation before
+                * inversion. Then the final Montgomery multiplication
+                * will cancel out with x2, which is still in Montgomery
+                * representation.
+                */
+               z2.FromMonty();
+               z2.Invert();
+               x2.MontyMul(z2);
+
+               /*
+                * x2 now contains the result.
+                */
+               x.Set(x2);
+               return 0xFFFFFFFF;
+       }
+
+       internal override uint EqCT(MutableECPoint Q)
+       {
+               MutableECPointCurve25519 R = SameCurve(Q);
+               return x.EqCT(R.x);
+       }
+
+       MutableECPointCurve25519 SameCurve(MutableECPoint Q)
+       {
+               MutableECPointCurve25519 R = Q as MutableECPointCurve25519;
+               if (R == null) {
+                       throw new CryptoException("Mixed curves");
+               }
+               return R;
+       }
+}
+
+}
diff --git a/Crypto/MutableECPointPrime.cs b/Crypto/MutableECPointPrime.cs
new file mode 100644 (file)
index 0000000..fa4b12c
--- /dev/null
@@ -0,0 +1,907 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * An implementation of MutableECPoint for curves in a prime field.
+ * Jacobian coordinates are used.
+ */
+
+internal class MutableECPointPrime : MutableECPoint {
+
+       /*
+        * Internal representation:
+        *    x = X / (Z^2)
+        *    y = Y / (Z^3)
+        * For the point at infinity, Z == 0.
+        *
+        * When 'affine' is true (0xFFFFFFFF), then mx and my are in
+        * normal representation, and mz is either 0 (point at infinity)
+        * or 1 (otherwise). When 'affine' is false (0x00000000), then
+        * mx, my and mz are in Montgomery representation.
+        *
+        * Note that in affine coordinates, the point at infinity admits
+        * several equivalent representations. In non-affine
+        * coordinates, all points have several equivalent
+        * representations.
+        */
+
+       ECCurvePrime curve;
+       ModInt mx, my, mz;
+       uint affine;
+       ModInt mt1, mt2, mt3, mt4, mt5;
+       ModInt ms1, ms2, ms3, ms4, ms5, ms6;
+
+       /*
+        * Create a new instance. It is initialized to the point at
+        * infinity.
+        */
+       internal MutableECPointPrime(ECCurvePrime curve)
+       {
+               this.curve = curve;
+               mx = curve.mp.Dup();
+               my = curve.mp.Dup();
+               mz = curve.mp.Dup();
+               mt1 = curve.mp.Dup();
+               mt2 = curve.mp.Dup();
+               mt3 = curve.mp.Dup();
+               mt4 = curve.mp.Dup();
+               mt5 = curve.mp.Dup();
+               ms1 = curve.mp.Dup();
+               ms2 = curve.mp.Dup();
+               ms3 = curve.mp.Dup();
+               ms4 = curve.mp.Dup();
+               ms5 = curve.mp.Dup();
+               ms6 = curve.mp.Dup();
+               affine = 0xFFFFFFFF;
+       }
+
+       internal override ECCurve Curve {
+               get {
+                       return curve;
+               }
+       }
+
+       internal override uint IsInfinityCT {
+               get {
+                       return mz.IsZeroCT;
+               }
+       }
+
+       internal override void Normalize()
+       {
+               ToAffine();
+       }
+
+       internal override byte[] Encode(bool compressed)
+       {
+               ToAffine();
+               if (IsInfinity) {
+                       return new byte[1];
+               }
+               if (compressed) {
+                       byte[] enc = new byte[curve.EncodedLengthCompressed];
+                       enc[0] = (byte)(0x02 + my.GetLSB());
+                       mx.Encode(enc, 1, enc.Length - 1);
+                       return enc;
+               } else {
+                       byte[] enc = new byte[curve.EncodedLength];
+                       int flen = (enc.Length - 1) >> 1;
+                       enc[0] = 0x04;
+                       mx.Encode(enc, 1, flen);
+                       my.Encode(enc, 1 + flen, flen);
+                       return enc;
+               }
+       }
+
+       internal override uint Encode(byte[] dst, bool compressed)
+       {
+               ToAffine();
+               if (compressed) {
+                       int len = curve.EncodedLengthCompressed;
+                       if (dst.Length != len) {
+                               throw new CryptoException(
+                                       "invalid output length");
+                       }
+                       dst[0] = (byte)(0x02 + my.GetLSB());
+                       mx.Encode(dst, 1, len - 1);
+               } else {
+                       int len = curve.EncodedLength;
+                       if (dst.Length != len) {
+                               throw new CryptoException(
+                                       "invalid output length");
+                       }
+                       int flen = (len - 1) >> 1;
+                       dst[0] = 0x04;
+                       mx.Encode(dst, 1, flen);
+                       my.Encode(dst, 1 + flen, flen);
+               }
+               return ~IsInfinityCT;
+       }
+
+       internal override uint DecodeCT(byte[] enc)
+       {
+               /*
+                * Format (specified in IEEE P1363, annex E):
+                *
+                *   0x00             point at infinity
+                *   0x02+b <X>       compressed, b = lsb of Y
+                *   0x04 <X> <Y>     uncompressed
+                *   0x06+b <X> <Y>   uncompressed, b = lsb of Y
+                *
+                * Coordinates X and Y are in unsigned big-endian
+                * notation with exactly the length of the modulus.
+                *
+                * We want constant-time decoding, up to the encoded
+                * length. This means that the four following situations
+                * can be differentiated:
+                * -- Point is zero (length = 1)
+                * -- Point is compressed (length = 1 + flen)
+                * -- Point is uncompressed or hybrid (length = 1 + 2*flen)
+                * -- Length is neither 1, 1+flen or 1+2*flen.
+                */
+
+               int flen = curve.flen;
+               uint good = 0xFFFFFFFF;
+               if (enc.Length == 1) {
+                       /*
+                        * 1-byte encoding is point at infinity; the
+                        * byte shall have value 0.
+                        */
+                       int z = enc[0];
+                       good &= ~(uint)((z | -z) >> 31);
+                       SetZero();
+               } else if (enc.Length == 1 + flen) {
+                       /*
+                        * Compressed encoding. Leading byte is 0x02 or
+                        * 0x03.
+                        */
+                       int z = (enc[0] & 0xFE) - 0x02;
+                       good &= ~(uint)((z | -z) >> 31);
+                       uint lsbValue = (uint)(enc[0] & 1);
+                       good &= mx.Decode(enc, 1, flen);
+                       RebuildY2();
+                       if (curve.pMod4 == 3) {
+                               good &= my.SqrtBlum();
+                       } else {
+                               /*
+                                * Square roots modulo a non-Blum prime
+                                * are a bit more complex. We do not
+                                * support them yet (TODO).
+                                */
+                               good = 0x00000000;
+                       }
+
+                       /*
+                        * Adjust Y depending on LSB.
+                        */
+                       mt1.Set(my);
+                       mt1.Negate();
+                       uint dn = (uint)-(int)(my.GetLSB() ^ lsbValue);
+                       my.CondCopy(mt1, dn);
+
+                       /*
+                        * A corner case: LSB adjustment works only if
+                        * Y != 0. If Y is 0 and requested LSB is 1,
+                        * then the decoding fails. Note that this case
+                        * cannot happen with usual prime curves, because
+                        * they have a prime order, implying that there is
+                        * no valid point such that Y = 0 (that would be
+                        * a point of order 2).
+                        */
+                       good &= ~(uint)-(int)(my.GetLSB() ^ lsbValue);
+
+                       mz.Set(1);
+               } else if (enc.Length == 1 + (flen << 1)) {
+                       /*
+                        * Uncompressed or hybrid. Leading byte is either
+                        * 0x04, 0x06 or 0x07. We verify that the X and
+                        * Y coordinates fulfill the curve equation.
+                        */
+                       int fb = enc[0];
+                       int z = (fb & 0xFC) - 0x04;
+                       good &= ~(uint)((z | -z) >> 31);
+                       z = fb - 0x05;
+                       good &= (uint)((z | -z) >> 31);
+                       good &= mx.Decode(enc, 1, flen);
+                       RebuildY2();
+                       mt1.Set(my);
+                       mt1.FromMonty();
+                       good &= my.Decode(enc, 1 + flen, flen);
+                       mt2.Set(my);
+                       mt2.MontySquare();
+                       good &= mt1.EqCT(mt2);
+
+                       /*
+                        * We must check the LSB for hybrid encoding.
+                        * The check fails if the encoding is marked as
+                        * hybrid AND the LSB does not match.
+                        */
+                       int lm = (fb >> 1) & ((int)my.GetLSB() ^ fb) & 1;
+                       good &= ~(uint)-lm;
+
+                       mz.Set(1);
+               } else {
+                       good = 0x00000000;
+               }
+
+               /*
+                * If decoding failed, then we force the value to 0.
+                * Otherwise, we got a value. Either way, this uses
+                * affine coordinates.
+                */
+               mx.CondCopy(curve.mp, ~good);
+               my.CondCopy(curve.mp, ~good);
+               mz.CondCopy(curve.mp, ~good);
+               affine = 0xFFFFFFFF;
+               return good;
+       }
+
+       internal override byte[] X {
+               get {
+                       ToAffine();
+                       return mx.Encode();
+               }
+       }
+
+       internal override byte[] Y {
+               get {
+                       ToAffine();
+                       return my.Encode();
+               }
+       }
+
+       internal override MutableECPoint Dup()
+       {
+               MutableECPointPrime Q = new MutableECPointPrime(curve);
+               Q.Set(this);
+               return Q;
+       }
+
+       internal void Set(byte[] X, byte[] Y, bool check)
+       {
+               mx.Decode(X);
+               my.Decode(Y);
+               mz.Set(1);
+               affine = 0xFFFFFFFF;
+               if (check) {
+                       CheckEquation();
+               }
+       }
+
+       internal void Set(ModInt X, ModInt Y, bool check)
+       {
+               mx.Set(X);
+               my.Set(Y);
+               mz.Set(1);
+               affine = 0xFFFFFFFF;
+               if (check) {
+                       CheckEquation();
+               }
+       }
+
+       void CheckEquation()
+       {
+               curve.RebuildY2(mx, mt1, mt2);
+               mt2.Set(my);
+               mt2.ToMonty();
+               mt2.MontyMul(my);
+               if (!mt1.Eq(mt2)) {
+                       throw new CryptoException(
+                               "Point is not on the curve");
+               }
+       }
+
+       internal override void SetZero()
+       {
+               mx.Set(0);
+               my.Set(0);
+               mz.Set(0);
+               affine = 0xFFFFFFFF;
+       }
+
+       internal override void Set(MutableECPoint Q)
+       {
+               MutableECPointPrime R = SameCurve(Q);
+               mx.Set(R.mx);
+               my.Set(R.my);
+               mz.Set(R.mz);
+               affine = R.affine;
+       }
+
+       internal override void Set(MutableECPoint Q, uint ctl)
+       {
+               MutableECPointPrime R = SameCurve(Q);
+               mx.CondCopy(R.mx, ctl);
+               my.CondCopy(R.my, ctl);
+               mz.CondCopy(R.mz, ctl);
+               affine ^= ctl & (affine ^ R.affine);
+       }
+
+       internal override void SetMux(uint ctl,
+               MutableECPoint P1, MutableECPoint P2)
+       {
+               SetMuxInner(ctl, SameCurve(P1), SameCurve(P2));
+       }
+
+       void SetMuxInner(uint ctl,
+               MutableECPointPrime P1, MutableECPointPrime P2)
+       {
+               mx.CopyMux(ctl, P1.mx, P2.mx);
+               my.CopyMux(ctl, P1.my, P2.my);
+               mz.CopyMux(ctl, P1.mz, P2.mz);
+               affine = P2.affine ^ (ctl & (P1.affine ^ P2.affine));
+       }
+
+       internal override void DoubleCT()
+       {
+               ToJacobian();
+
+               /*
+                * Formulas are:
+                *   S = 4*X*Y^2
+                *   M = 3*X^2 + a*Z^4
+                *   X' = M^2 - 2*S
+                *   Y' = M*(S - X') - 8*Y^4
+                *   Z' = 2*Y*Z
+                *
+                * These formulas also happen to work properly (with our
+                * chosen representation) when the source point has
+                * order 2 (Y = 0 implies Z' = 0) and when the source
+                * point is already the point at infinity (Z = 0 implies
+                * Z' = 0).
+                *
+                * When a = -3, the value of M can be computed with the
+                * more efficient formula:
+                *   M = 3*(X+Z^2)*(X-Z^2)
+                */
+
+               /*
+                * Compute M in t1.
+                */
+               if (curve.aIsM3) {
+                       /*
+                        * Set t1 = Z^2.
+                        */
+                       mt1.Set(mz);
+                       mt1.MontySquare();
+
+                       /*
+                        * Set t2 = X-Z^2 and then t1 = X+Z^2.
+                        */
+                       mt2.Set(mx);
+                       mt2.Sub(mt1);
+                       mt1.Add(mx);
+
+                       /*
+                        * Set t1 = 3*(X+Z^2)*(X-Z^2).
+                        */
+                       mt1.MontyMul(mt2);
+                       mt2.Set(mt1);
+                       mt1.Add(mt2);
+                       mt1.Add(mt2);
+               } else {
+                       /*
+                        * Set t1 = 3*X^2.
+                        */
+                       mt1.Set(mx);
+                       mt1.MontySquare();
+                       mt2.Set(mt1);
+                       mt1.Add(mt2);
+                       mt1.Add(mt2);
+
+                       /*
+                        * Set t2 = a*Z^4.
+                        */
+                       mt2.Set(mz);
+                       mt2.MontySquare();
+                       mt2.MontySquare();
+                       mt2.MontyMul(curve.ma);
+
+                       /*
+                        * Set t1 = 3*X^2 + a*Z^4.
+                        */
+                       mt1.Add(mt2);
+               }
+
+               /*
+                * Compute S = 4*X*Y^2 in t2. We also save 2*Y^2 in mt3.
+                */
+               mt2.Set(my);
+               mt2.MontySquare();
+               mt2.Add(mt2);
+               mt3.Set(mt2);
+               mt2.Add(mt2);
+               mt2.MontyMul(mx);
+
+               /*
+                * Compute X' = M^2 - 2*S.
+                */
+               mx.Set(mt1);
+               mx.MontySquare();
+               mx.Sub(mt2);
+               mx.Sub(mt2);
+
+               /*
+                * Compute Z' = 2*Y*Z.
+                */
+               mz.MontyMul(my);
+               mz.Add(mz);
+
+               /*
+                * Compute Y' = M*(S - X') - 8*Y^4. We already have
+                * 4*Y^2 in t3.
+                */
+               mt2.Sub(mx);
+               mt2.MontyMul(mt1);
+               mt3.MontySquare();
+               mt3.Add(mt3);
+               my.Set(mt2);
+               my.Sub(mt3);
+       }
+
+       internal override uint AddCT(MutableECPoint Q)
+       {
+               MutableECPointPrime P2 = SameCurve(Q);
+
+               if (P2.affine != 0) {
+                       ms4.Set(P2.mx);
+                       ms5.Set(P2.my);
+                       ms6.Set(P2.mz);
+                       ms4.ToMonty();
+                       ms5.ToMonty();
+                       ms6.SetMonty(~ms6.IsZeroCT);
+                       return AddCTInner(ms4, ms5, ms6, true);
+               } else {
+                       return AddCTInner(P2.mx, P2.my, P2.mz, false);
+               }
+       }
+
+       /*
+        * Inner function for addition. The Jacobian coordinates for
+        * the operand are provided in Montogomery representation. If
+        * p2affine is true, then it is guaranteed that p2z is 1
+        * (converted to Montogomery).
+        */
+       uint AddCTInner(ModInt p2x, ModInt p2y, ModInt p2z, bool p2affine)
+       {
+               /*
+                * In this comment, the two operands are called P1 and
+                * P2. P1 is this instance; P2 is the operand. Coordinates
+                * of P1 are (X1,Y1,Z1). Coordinates of P2 are (X2,Y2,Z2).
+                *
+                * Formulas:
+                *   U1 = X1 * Z2^2
+                *   U2 = X2 * Z1^2
+                *   S1 = Y1 * Z2^3
+                *   S2 = Y2 * Z1^3
+                *   H = U2 - U1
+                *   R = S2 - S1
+                *   X3 = R^2 - H^3 - 2*U1*H^2
+                *   Y3 = R*(U1*H^2 - X3) - S1*H^3
+                *   Z3 = H*Z1*Z2
+                *
+                * If both P1 and P2 are 0, then the formulas yield 0,
+                * which is fine. If one of P1 and P2 is 0 (but not both),
+                * then we get 0 as result, which is wrong and must be
+                * fixed at the end.
+                *
+                * If U1 == U2 and S1 == S2 then this means that either
+                * P1 or P2 is 0 (or both), or P1 == P2. In the latter
+                * case, the formulas are wrong and we must report
+                * an error.
+                *
+                * If U1 == U2 and S1 != S2 then P1 + P2 = 0. We get H = 0,
+                * which implies that we obtain the point at infinity,
+                * which is fine.
+                */
+
+               uint P1IsZero = mz.IsZeroCT;
+               uint P2IsZero = p2z.IsZeroCT;
+
+               ToJacobian();
+
+               /*
+                * Save this value, in case the operand turns out to
+                * be the point at infinity.
+                */
+               ms1.Set(mx);
+               ms2.Set(my);
+               ms3.Set(mz);
+
+               /*
+                * Compute U1 = X1*Z2^2 in t1, and S1 = Y1*Z2^3 in t3.
+                */
+               if (p2affine) {
+                       mt1.Set(mx);
+                       mt3.Set(my);
+               } else {
+                       mt3.Set(p2z);
+                       mt3.MontySquare();
+                       mt1.Set(mx);
+                       mt1.MontyMul(mt3);
+                       mt3.MontyMul(p2z);
+                       mt3.MontyMul(my);
+               }
+               //PrintMR(" u1 = x1*z2^2", mt1);
+               //PrintMR(" s1 = y1*z2^3", mt3);
+
+               /*
+                * Compute U2 = X2*Z1^2 in t2, and S2 = Y2*Z1^3 in t4.
+                */
+               mt4.Set(mz);
+               mt4.MontySquare();
+               mt2.Set(p2x);
+               mt2.MontyMul(mt4);
+               mt4.MontyMul(mz);
+               mt4.MontyMul(p2y);
+               //PrintMR(" u2 = x2*z1^2", mt2);
+               //PrintMR(" s2 = y2*z1^3", mt4);
+
+               /*
+                * Compute H = U2 - U1 in t2, and R = S2 - S1 in t4.
+                */
+               mt2.Sub(mt1);
+               mt4.Sub(mt3);
+               //PrintMR(" h = u2-u1", mt2);
+               //PrintMR(" r = s2-s1", mt4);
+
+               /*
+                * If both H and R are 0, then we may have a problem
+                * (either P1 == P2, or P1 == 0, or P2 == 0).
+                */
+               uint formProb = mt2.IsZeroCT & mt4.IsZeroCT;
+
+               /*
+                * Compute U1*H^2 in t1 and H^3 in t5.
+                */
+               mt5.Set(mt2);
+               mt5.MontySquare();
+               mt1.MontyMul(mt5);
+               mt5.MontyMul(mt2);
+               //PrintMR(" u1*h^2", mt1);
+               //PrintMR(" h^3", mt5);
+
+               /*
+                * Compute X3 = R^2 - H^3 - 2*U1*H^2.
+                */
+               mx.Set(mt4);
+               mx.MontySquare();
+               mx.Sub(mt5);
+               mx.Sub(mt1);
+               mx.Sub(mt1);
+               //PrintMR(" x3 = r^2-h^3-2*u1*h^2", mx);
+
+               /*
+                * Compute Y3 = R*(U1*H^2 - X3) - S1*H^3.
+                */
+               mt1.Sub(mx);
+               mt1.MontyMul(mt4);
+               mt5.MontyMul(mt3);
+               mt1.Sub(mt5);
+               my.Set(mt1);
+               //PrintMR(" y3 = r*(u1*h^2-x3)-s1*h^3", my);
+
+               /*
+                * Compute Z3 = H*Z1*Z2.
+                */
+               mz.MontyMul(mt2);
+               if (!p2affine) {
+                       mz.MontyMul(p2z);
+               }
+               //PrintMR(" z3 = h*z1*z2", mz);
+
+               /*
+                * Fixup: handle the cases where P1 = 0 or P2 = 0.
+                */
+               mx.CondCopy(ms1, P2IsZero);
+               my.CondCopy(ms2, P2IsZero);
+               mz.CondCopy(ms3, P2IsZero);
+               mx.CondCopy(p2x, P1IsZero);
+               my.CondCopy(p2y, P1IsZero);
+               mz.CondCopy(p2z, P1IsZero);
+
+               /*
+                * Report failure when P1 == P2, except when one of
+                * the points was zero (or both) because that case
+                * was properly handled.
+                */
+               return (~formProb) | P1IsZero | P2IsZero;
+       }
+
+       internal override void NegCT()
+       {
+               my.Negate();
+       }
+
+       internal override uint MulSpecCT(byte[] n)
+       {
+               uint good = 0xFFFFFFFF;
+
+               /*
+                * Create and populate window.
+                *
+                * If this instance is 0, then we only add 0 to 0 and
+                * double 0, for which DoubleCT() and AddCT() work
+                * properly.
+                *
+                * If this instance (P) is non-zero, then x*P for all
+                * x in the 1..16 range shall be non-zero and distinct,
+                * since the subgroup order is prime and at least 17.
+                * Thus, we never add two equal points together in the
+                * window construction.
+                *
+                * We MUST ensure that all points are in the same
+                * coordinate convention (affine or Jacobian) to ensure
+                * constant-time execution. TODO: measure to see which
+                * is best: all affine or all Jacobian. All affine implies
+                * 14 or 15 extra divisions, but saves a few hundreds of
+                * multiplications.
+                */
+               MutableECPointPrime[] w = new MutableECPointPrime[16];
+               w[0] = new MutableECPointPrime(curve);
+               w[0].ToJacobian();
+               w[1] = new MutableECPointPrime(curve);
+               w[1].Set(this);
+               w[1].ToJacobian();
+               for (int i = 2; (i + 1) < w.Length; i += 2) {
+                       w[i] = new MutableECPointPrime(curve);
+                       w[i].Set(w[i >> 1]);
+                       w[i].DoubleCT();
+                       w[i + 1] = new MutableECPointPrime(curve);
+                       w[i + 1].Set(w[i]);
+                       good &= w[i + 1].AddCT(this);
+               }
+
+               /* obsolete
+               for (int i = 0; i < w.Length; i ++) {
+                       w[i].ToAffine();
+                       w[i].Print("Win " + i);
+                       w[i].ToJacobian();
+               }
+               Console.WriteLine("good = {0}", (int)good);
+               */
+
+               /*
+                * Set this value to 0. We also set it already to
+                * Jacobian coordinates, since it will be done that
+                * way anyway. This instance will serve as accumulator.
+                */
+               mx.Set(0);
+               my.Set(0);
+               mz.Set(0);
+               affine = 0x00000000;
+
+               /*
+                * We process the multiplier by 4-bit nibbles, starting
+                * with the most-significant one (the high nibble of the
+                * first byte, since we use big-endian notation).
+                *
+                * For each nibble, we perform a constant-time lookup
+                * in the window, to obtain the point to add to the
+                * current value of the accumulator. Thanks to the
+                * conditions on the operands (prime subgroup order and
+                * so on), all the additions below must work.
+                */
+               MutableECPointPrime t = new MutableECPointPrime(curve);
+               for (int i = (n.Length << 1) - 1; i >= 0; i --) {
+                       int b = n[n.Length - 1 - (i >> 1)];
+                       int j = (b >> ((i & 1) << 2)) & 0x0F;
+                       for (int k = 0; k < 16; k ++) {
+                               t.Set(w[k], ~(uint)(((j - k) | (k - j)) >> 31));
+                       }
+                       good &= AddCT(t);
+                       if (i > 0) {
+                               DoubleCT();
+                               DoubleCT();
+                               DoubleCT();
+                               DoubleCT();
+                       }
+               }
+
+               return good;
+       }
+
+       internal override uint EqCT(MutableECPoint Q)
+       {
+               MutableECPointPrime R = SameCurve(Q);
+               if (affine != 0) {
+                       if (R.affine != 0) {
+                               return mx.EqCT(R.mx)
+                                       & my.EqCT(R.my)
+                                       & mz.EqCT(R.mz);
+                       } else {
+                               return EqCTMixed(R, this);
+                       }
+               } else if (R.affine != 0) {
+                       return EqCTMixed(this, R);
+               }
+
+               /*
+                * Both points are in Jacobian coordinates.
+                * If Z1 and Z2 are non-zero, then equality is
+                * achieved if and only if both following equations
+                * are true:
+                *     X1*(Z2^2) = X2*(Z1^2)
+                *     Y1*(Z2^3) = Y2*(Z1^3)
+                * If Z1 or Z2 is zero, then equality is achieved
+                * if and only if both are zero.
+                */
+               mt1.Set(mz);
+               mt1.MontySquare();
+               mt2.Set(R.mz);
+               mt2.MontySquare();
+               mt3.Set(mx);
+               mt3.MontyMul(mt2);
+               mt4.Set(R.mx);
+               mt4.MontyMul(mt1);
+               uint r = mt3.EqCT(mt4);
+               mt1.MontyMul(mz);
+               mt2.MontyMul(R.mz);
+               mt3.Set(my);
+               mt3.MontyMul(mt2);
+               mt4.Set(R.my);
+               mt4.MontyMul(mt1);
+               r &= mt3.EqCT(mt4);
+               uint z1z = mz.IsZeroCT;
+               uint z2z = R.mz.IsZeroCT;
+               return (r & ~(z1z | z2z)) ^ (z1z & z2z);
+       }
+
+       /*
+        * Mixed comparison: P1 is in Jacobian coordinates, P2 is in
+        * affine coordinates.
+        */
+       uint EqCTMixed(MutableECPointPrime P1, MutableECPointPrime P2)
+       {
+               /*
+                * If either P1 or P2 is infinity, then they are equal
+                * if and only if they both are infinity.
+                *
+                * If neither is infinity, then we must check the following:
+                *    X1 = X2*(Z1^2)
+                *    Y1 = Y2*(Z1^3)
+                * Beware that X1, Y1 and Z1 are in Montgomery representation,
+                * while X2 and Y2 are not.
+                */
+               mt1.Set(P1.mz);
+               mt1.MontySquare();
+               mt2.Set(P2.mx);
+               mt2.MontyMul(mt1);
+               mt3.Set(P1.mx);
+               mt3.FromMonty();
+               uint r = mt2.EqCT(mt3);
+               mt1.MontyMul(P1.mz);
+               mt1.MontyMul(P2.my);
+               mt2.Set(P1.my);
+               mt2.FromMonty();
+               r &= mt1.EqCT(mt2);
+               uint z1z = P1.mz.IsZeroCT;
+               uint z2z = P2.mz.IsZeroCT;
+               return (r & ~(z1z | z2z)) ^ (z1z & z2z);
+       }
+
+       MutableECPointPrime SameCurve(MutableECPoint Q)
+       {
+               MutableECPointPrime R = Q as MutableECPointPrime;
+               if (R == null || !curve.Equals(R.curve)) {
+                       throw new CryptoException("Mixed curves");
+               }
+               return R;
+       }
+
+       /*
+        * Convert to Jabobian coordinates (if not already done).
+        */
+       void ToJacobian()
+       {
+               if (affine == 0) {
+                       return;
+               }
+
+               /*
+                * Since Z = 0 or 1 in affine coordinates, we can
+                * use SetMonty().
+                */
+               mx.ToMonty();
+               my.ToMonty();
+               mz.SetMonty(~mz.IsZeroCT);
+               affine = 0x00000000;
+       }
+
+       /*
+        * Convert to affine coordinates (if not already done).
+        */
+       void ToAffine()
+       {
+               if (affine != 0) {
+                       return;
+               }
+
+               /*
+                * Divisions are expensive, so we want to make only one,
+                * not two. This involves some games with Montgomery
+                * representation.
+                *
+                * A number a in Montgomery representation means that
+                * the value we have is equal to aR. Montgomery
+                * multiplication of a by b yields ab/R (so, if we
+                * apply it to aR and bR, we get abR).
+                */
+
+               /* Save Z*R in mt1. */
+               mt1.Set(mz);
+
+               /* Compute Z^3 in mz. */
+               mz.MontySquare();
+               mz.MontyMul(mt1);
+               mz.FromMonty();
+
+               /* Compute t2 = 1/Z^3. */
+               mt2.Set(mz);
+               mt2.Invert();
+               uint cc = ~mt2.IsZeroCT;
+               
+               /* Compute y. */
+               my.MontyMul(mt2);
+
+               /* Compute t2 = 1/Z^2. */
+               mt2.MontyMul(mt1);
+
+               /* Compute x. */
+               mx.MontyMul(mt2);
+
+               /*
+                * If the point is infinity (division by Z^2 failed),
+                * then set all coordinates to 0. Otherwise, set mz
+                * to exactly 1.
+                */
+               mx.CondCopy(curve.mp, ~cc);
+               my.CondCopy(curve.mp, ~cc);
+               mz.Set((int)cc & 1);
+
+               affine = 0xFFFFFFFF;
+       }
+
+       /*
+        * Compute Y^2 into my, using the value in mx as X. Both values
+        * are in normal (non-Montgomery) representation.
+        */
+       void RebuildY2()
+       {
+               my.Set(mx);
+               my.ToMonty();
+               mt1.Set(my);
+               my.MontySquare();
+               my.MontyMul(mx);
+               mt1.MontyMul(curve.ma);
+               my.Add(mt1);
+               my.Add(curve.mb);
+       }
+}
+
+}
diff --git a/Crypto/NIST.cs b/Crypto/NIST.cs
new file mode 100644 (file)
index 0000000..b21f277
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class contains static definitions for the NIST elliptic
+ * curves.
+ */
+
+public class NIST {
+
+       // public static ECCurve P192;
+       // public static ECCurve P224;
+       public static ECCurve P256;
+       public static ECCurve P384;
+       public static ECCurve P521;
+
+       static NIST()
+       {
+               P256 = MakePrime(
+                       "P-256",
+                       "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF",
+                       "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC",
+                       "5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B",
+                       "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296",
+                       "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5",
+                       "FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551",
+                       "01");
+               P384 = MakePrime(
+                       "P-384",
+                       "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF",
+                       "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC",
+                       "B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF",
+                       "AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7",
+                       "3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F",
+                       "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973",
+                       "01");
+               P521 = MakePrime(
+                       "P-521",
+                       "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
+                       "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC",
+                       "0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00",
+                       "00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66",
+                       "011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650",
+                       "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409",
+                       "01");
+       }
+
+       static ECCurvePrime MakePrime(string name, string smod,
+               string sa, string sb, string sgx, string sgy,
+               string sso, string scf)
+       {
+               return new ECCurvePrime(
+                       name,
+                       ToBytes(smod), ToBytes(sa), ToBytes(sb),
+                       ToBytes(sgx), ToBytes(sgy),
+                       ToBytes(sso), ToBytes(scf));
+       }
+
+       static byte[] ToBytes(string s)
+       {
+               int len = ToBytes(s, null);
+               byte[] dst = new byte[len];
+               ToBytes(s, dst);
+               return dst;
+       }
+
+       static int ToBytes(string str, byte[] dst)
+       {
+               int off = 0;
+               bool z = true;
+               int acc = 0;
+               foreach (char c in str) {
+                       int d;
+                       if (c >= '0' && c <= '9') {
+                               d = c - '0';
+                       } else if (c >= 'A' && c <= 'F') {
+                               d = c - ('A' - 10);
+                       } else if (c >= 'a' && c <= 'f') {
+                               d = c - ('a' - 10);
+                       } else if (c == ' ' || c == '\t' || c == ':') {
+                               continue;
+                       } else {
+                               throw new ArgumentException(String.Format(
+                                       "not hex: U+{0:X4}", (int)c));
+                       }
+                       if (z) {
+                               acc = d;
+                       } else {
+                               if (dst != null) {
+                                       dst[off] = (byte)((acc << 4) + d);
+                               }
+                               off ++;
+                       }
+                       z = !z;
+               }
+               if (!z) {
+                       throw new ArgumentException("final half byte");
+               }
+               return off;
+       }
+}
+
+}
diff --git a/Crypto/Poly1305.cs b/Crypto/Poly1305.cs
new file mode 100644 (file)
index 0000000..fb63429
--- /dev/null
@@ -0,0 +1,327 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class implements Poly1305, when used with ChaCha20. The
+ * ChaCha20 instance (already set with the secret key) is passed as
+ * parameter. Instances are not thread-safe.
+ */
+
+public sealed class Poly1305 {
+
+       /*
+        * The ChaCha20 instance to use for encryption and decryption.
+        * That instance MUST be set, and it must have been initialised
+        * with the key to use.
+        */
+       public ChaCha20 ChaCha {
+               get; set;
+       }
+
+       byte[] pkey;
+       uint[] r;
+       uint[] acc;
+       byte[] foot;
+       byte[] tmp;
+
+       /*
+        * Create a new instance. The ChaCha20 instance to use MUST be
+        * set in the 'ChaCha' property.
+        */
+       public Poly1305()
+       {
+               pkey = new byte[32];
+               r = new uint[5];
+               acc = new uint[5];
+               foot = new byte[16];
+               tmp = new byte[16];
+       }
+
+       /*
+        * Run Poly1305 and ChaCha20 on the provided elements.
+        *
+        *   iv        Nonce for ChaCha20, exactly 12 bytes
+        *   data      data to encrypt or decrypt (buffer, off + len)
+        *   aad       additional authenticated data (buffer, offAAD + lenAAD)
+        *   tag       destination for computed authentication tag
+        *   encrypt   true to encrypt, false to decrypt
+        */
+       public void Run(byte[] iv,
+               byte[] data, int off, int len,
+               byte[] aad, int offAAD, int lenAAD,
+               byte[] tag, bool encrypt)
+       {
+               /*
+                * Compute Poly1305 key.
+                */
+               for (int i = 0; i < pkey.Length; i ++) {
+                       pkey[i] = 0;
+               }
+               ChaCha.Run(iv, 0, pkey);
+
+               /*
+                * If encrypting, ChaCha20 must run first (the MAC is
+                * computed on the ciphertext).
+                */
+               if (encrypt) {
+                       ChaCha.Run(iv, 1, data, off, len);
+               }
+
+               /*
+                * Decode the 'r' value into 26-bit words, with the
+                * "clamping" operation applied.
+                */
+               r[0] = Dec32le(pkey, 0) & 0x03FFFFFF;
+               r[1] = (Dec32le(pkey, 3) >> 2) & 0x03FFFF03;
+               r[2] = (Dec32le(pkey,  6) >> 4) & 0x03FFC0FF;
+               r[3] = (Dec32le(pkey,  9) >> 6) & 0x03F03FFF;
+               r[4] = (Dec32le(pkey, 12) >> 8) & 0x000FFFFF;
+
+               /*
+                * Accumulator is 0.
+                */
+               acc[0] = 0;
+               acc[1] = 0;
+               acc[2] = 0;
+               acc[3] = 0;
+               acc[4] = 0;
+
+               /*
+                * Process AAD, ciphertext and footer.
+                */
+               Enc32le(lenAAD, foot, 0);
+               Enc32le(0, foot, 4);
+               Enc32le(len, foot, 8);
+               Enc32le(0, foot, 12);
+               RunInner(aad, offAAD, lenAAD);
+               RunInner(data, off, len);
+               RunInner(foot, 0, 16);
+
+               /*
+                * Finalize modular reduction. The output of RunInner() is
+                * already mostly reduced: only acc[1] may be (very slightly)
+                * above 2^26. Thus, we only need one loop, back to acc[1].
+                */
+               uint cc = 0;
+               for (int i = 1; i <= 6; i ++) {
+                       int j;
+
+                       j = (i >= 5) ? i - 5 : i;
+                       acc[j] += cc;
+                       cc = acc[j] >> 26;
+                       acc[j] &= 0x03FFFFFF;
+               }
+
+               /*
+                * The final value may still be in the 2^130-5..2^130-1
+                * range, in which case an additional subtraction must be
+                * performed, with constant-time code.
+                */
+               cc = (uint)((int)(0x03FFFFFA - acc[0]) >> 31);
+               for (int i = 1; i < 5; i ++) {
+                       int z = (int)(acc[i] - 0x03FFFFFF);
+                       cc &= ~(uint)((z | -z) >> 31);
+               }
+               cc &= 5;
+               for (int i = 0; i < 5; i ++) {
+                       uint t = acc[i] + cc;
+                       cc = t >> 26;
+                       acc[i] = t & 0x03FFFFFF;
+               }
+
+               /*
+                * The tag is the sum of the 's' value (second half of
+                * the pkey[] array, little-endian encoding) and the
+                * current accumulator value. This addition is done modulo
+                * 2^128, i.e. with a simple truncation.
+                */
+               cc = 0;
+               uint aw = 0;
+               int awLen = 0;
+               for (int i = 0, j = 0; i < 16; i ++) {
+                       if (awLen < 8) {
+                               /*
+                                * We "refill" our running byte buffer with
+                                * a new extra accumulator word. Note that
+                                * 'awLen' is always even, so at this point
+                                * it must be 6 or less; since accumulator
+                                * words fit on 32 bits, the operation
+                                * below does not lose any bit.
+                                */
+                               aw |= acc[j ++] << awLen;
+                               awLen += 26;
+                       }
+                       uint tb = (aw & 0xFF) + cc + pkey[16 + i];
+                       aw >>= 8;
+                       awLen -= 8;
+                       tag[i] = (byte)tb;
+                       cc = tb >> 8;
+               }
+
+               /*
+                * If decrypting, then we still have the ciphertext at
+                * this point, and we must perform the decryption.
+                */
+               if (!encrypt) {
+                       ChaCha.Run(iv, 1, data, off, len);
+               }
+       }
+
+       /*
+        * Inner processing of the provided data. The accumulator and 'r'
+        * value are set in the instance fields, and must be updated.
+        * All accumulator words fit on 26 bits each, except the second
+        * (acc[1]) which may be very slightly above 2^26.
+        */
+       void RunInner(byte[] data, int off, int len)
+       {
+               /*
+                * Implementation is inspired from the public-domain code
+                * available there:
+                *    https://github.com/floodyberry/poly1305-donna
+                */
+               uint r0 = r[0];
+               uint r1 = r[1];
+               uint r2 = r[2];
+               uint r3 = r[3];
+               uint r4 = r[4];
+
+               uint u1 = r1 * 5;
+               uint u2 = r2 * 5;
+               uint u3 = r3 * 5;
+               uint u4 = r4 * 5;
+
+               uint a0 = acc[0];
+               uint a1 = acc[1];
+               uint a2 = acc[2];
+               uint a3 = acc[3];
+               uint a4 = acc[4];
+
+               while (len > 0) {
+                       if (len < 16) {
+                               Array.Copy(data, off, tmp, 0, len);
+                               for (int i = len; i < 16; i ++) {
+                                       tmp[i] = 0;
+                               }
+                               data = tmp;
+                               off = 0;
+                       }
+
+                       /*
+                        * Decode next block, with the "high bit" applied,
+                        * and add that value to the accumulator.
+                        */
+                       a0 += Dec32le(data, off) & 0x03FFFFFF;
+                       a1 += (Dec32le(data, off +  3) >> 2) & 0x03FFFFFF;
+                       a2 += (Dec32le(data, off +  6) >> 4) & 0x03FFFFFF;
+                       a3 += (Dec32le(data, off +  9) >> 6) & 0x03FFFFFF;
+                       a4 += (Dec32le(data, off + 12) >> 8) | 0x01000000;
+
+                       /*
+                        * Compute multiplication. All elementary
+                        * multiplications are 32x32->64.
+                        */
+                       ulong w0 = (ulong)a0 * r0
+                               + (ulong)a1 * u4
+                               + (ulong)a2 * u3
+                               + (ulong)a3 * u2
+                               + (ulong)a4 * u1;
+                       ulong w1 = (ulong)a0 * r1
+                               + (ulong)a1 * r0
+                               + (ulong)a2 * u4
+                               + (ulong)a3 * u3
+                               + (ulong)a4 * u2;
+                       ulong w2 = (ulong)a0 * r2
+                               + (ulong)a1 * r1
+                               + (ulong)a2 * r0
+                               + (ulong)a3 * u4
+                               + (ulong)a4 * u3;
+                       ulong w3 = (ulong)a0 * r3
+                               + (ulong)a1 * r2
+                               + (ulong)a2 * r1
+                               + (ulong)a3 * r0
+                               + (ulong)a4 * u4;
+                       ulong w4 = (ulong)a0 * r4
+                               + (ulong)a1 * r3
+                               + (ulong)a2 * r2
+                               + (ulong)a3 * r1
+                               + (ulong)a4 * r0;
+
+                       /*
+                        * Most of the modular reduction was done by using
+                        * the 'u*' multipliers. We still need to do some
+                        * carry propagation.
+                        */
+                       ulong c;
+                       c = w0 >> 26;
+                       a0 = (uint)w0 & 0x03FFFFFF;
+                       w1 += c;
+                       c = w1 >> 26;
+                       a1 = (uint)w1 & 0x03FFFFFF;
+                       w2 += c;
+                       c = w2 >> 26;
+                       a2 = (uint)w2 & 0x03FFFFFF;
+                       w3 += c;
+                       c = w3 >> 26;
+                       a3 = (uint)w3 & 0x03FFFFFF;
+                       w4 += c;
+                       c = w4 >> 26;
+                       a4 = (uint)w4 & 0x03FFFFFF;
+                       a0 += (uint)c * 5;
+                       a1 += a0 >> 26;
+                       a0 &= 0x03FFFFFF;
+
+                       off += 16;
+                       len -= 16;
+               }
+
+               acc[0] = a0;
+               acc[1] = a1;
+               acc[2] = a2;
+               acc[3] = a3;
+               acc[4] = a4;
+       }
+
+       static uint Dec32le(byte[] buf, int off)
+       {
+               return (uint)buf[off]
+                       | ((uint)buf[off + 1] << 8)
+                       | ((uint)buf[off + 2] << 16)
+                       | ((uint)buf[off + 3] << 24);
+       }
+
+       static void Enc32le(int x, byte[] buf, int off)
+       {
+               buf[off] = (byte)x;
+               buf[off + 1] = (byte)(x >> 8);
+               buf[off + 2] = (byte)(x >> 16);
+               buf[off + 3] = (byte)(x >> 24);
+       }
+}
+
+}
diff --git a/Crypto/RFC6979.cs b/Crypto/RFC6979.cs
new file mode 100644 (file)
index 0000000..03a77b9
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class implements the computation of the transient secret value
+ * "k" for DSA and ECDSA, using the method described in RFC 6979. It
+ * can perform the deterministic computation, and optionally inject
+ * extra random bytes when randomized signatures are needed.
+ */
+
+class RFC6979 {
+
+       HMAC_DRBG drbg;
+       byte[] q;
+       int qlen;
+       ModInt mh;
+
+       internal RFC6979(IDigest h, byte[] q, byte[] x,
+               byte[] hv, bool deterministic)
+               : this(h, q, x, hv, 0, hv.Length, deterministic)
+       {
+       }
+
+       internal RFC6979(IDigest h, byte[] q, byte[] x,
+               byte[] hv, int hvOff, int hvLen, bool deterministic)
+       {
+               if (h == null) {
+                       h = new SHA256();
+               } else {
+                       h = h.Dup();
+                       h.Reset();
+               }
+               drbg = new HMAC_DRBG(h);
+               mh = new ModInt(q);
+               qlen = mh.ModBitLength;
+               int qolen = (qlen + 7) >> 3;
+               this.q = new byte[qolen];
+               Array.Copy(q, q.Length - qolen, this.q, 0, qolen);
+               int hlen = hvLen << 3;
+               if (hlen > qlen) {
+                       byte[] htmp = new byte[hvLen];
+                       Array.Copy(hv, hvOff, htmp, 0, hv.Length);
+                       BigInt.RShift(htmp, hlen - qlen);
+                       hv = htmp;
+                       hvOff = 0;
+               }
+               mh.DecodeReduce(hv, hvOff, hvLen);
+               ModInt mx = mh.Dup();
+               mx.Decode(x);
+
+               byte[] seed = new byte[(qolen << 1) + (deterministic ? 0 : 32)];
+               mx.Encode(seed, 0, qolen);
+               mh.Encode(seed, qolen, qolen);
+               if (!deterministic) {
+                       RNG.GetBytes(seed, qolen << 1,
+                               seed.Length - (qolen << 1));
+               }
+               drbg.SetSeed(seed);
+       }
+
+       internal ModInt GetHashMod()
+       {
+               return mh.Dup();
+       }
+
+       internal void NextK(byte[] k)
+       {
+               for (;;) {
+                       drbg.GetBytes(k);
+                       BigInt.RShift(k, (k.Length << 3) - qlen);
+                       if (!BigInt.IsZero(k) && BigInt.CompareCT(k, q) < 0) {
+                               return;
+                       }
+               }
+       }
+}
+
+}
diff --git a/Crypto/RNG.cs b/Crypto/RNG.cs
new file mode 100644 (file)
index 0000000..ba4d5a9
--- /dev/null
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using NC = System.Security.Cryptography;
+
+namespace Crypto {
+
+/*
+ * Random generator.
+ */
+
+public sealed class RNG {
+
+       /*
+        * To ensure efficient generation of random numbers, we use
+        * our own PRNG, seeded with a strong value (from the operating
+        * system), and based on AES-CTR. We obtain a random 128-bit
+        * key from the OS (RNGCryptoServiceProvider); then we use it
+        * to encrypt successive values for a 128-bit counter (also
+        * initialized from RNGCryptoServiceProvider). This is AES-CTR
+        * mode and thus provably as strong as AES-128 encryption as it
+        * is practiced in SSL/TLS.
+        *
+        * A mutex is used to ensure safe access in a multi-threaded
+        * context. Once initialized, random generation proceeds at
+        * the same speed as AES encryption, i.e. fast enough for
+        * our purposes.
+        *
+        * As a special action for debugging, it is possible to reset
+        * the state to an explicit seed value. Of course, this tends
+        * to kill security, so it should be used only to make actions
+        * reproducible, as part of systematic tests.
+        */
+
+       static object rngMutex = new object();
+       static IBlockCipher rngAES = null;
+       static byte[] counter, rblock;
+
+       static void Init()
+       {
+               if (rngAES == null) {
+                       NC.RNGCryptoServiceProvider srng =
+                               new NC.RNGCryptoServiceProvider();
+                       byte[] key = new byte[16];
+                       byte[] iv = new byte[16];
+                       srng.GetBytes(key);
+                       srng.GetBytes(iv);
+                       Init(key, iv);
+               }
+       }
+
+       static void Init(byte[] key, byte[] iv)
+       {
+               if (rngAES == null) {
+                       rngAES = new AES();
+                       counter = new byte[16];
+                       rblock = new byte[16];
+               }
+               rngAES.SetKey(key);
+               Array.Copy(iv, 0, rblock, 0, 16);
+       }
+
+       static void NextBlock()
+       {
+               int len = counter.Length;
+               int carry = 1;
+               for (int i = 0; i < len; i ++) {
+                       int v = counter[i] + carry;
+                       counter[i] = (byte)v;
+                       carry = v >> 8;
+               }
+               Array.Copy(counter, 0, rblock, 0, len);
+               rngAES.BlockEncrypt(rblock);
+       }
+
+       /*
+        * Set or reset the state to the provided seed. All subsequent
+        * output will depend only on that seed value. This function shall
+        * be used ONLY for debug/test purposes, since it replaces the
+        * automatic seeding that uses OS-provided entropy.
+        */
+       public static void SetSeed(byte[] seed)
+       {
+               byte[] s32 = new SHA256().Hash(seed);
+               byte[] key = new byte[16];
+               byte[] iv = new byte[16];
+               Array.Copy(s32, 0, key, 0, 16);
+               Array.Copy(s32, 16, iv, 0, 16);
+               lock (rngMutex) {
+                       Init(key, iv);
+               }
+       }
+
+       /*
+        * Fill the provided array with random bytes.
+        */
+       public static void GetBytes(byte[] buf)
+       {
+               GetBytes(buf, 0, buf.Length);
+       }
+
+       /*
+        * Fill the provided array chunk with random bytes.
+        */
+       public static void GetBytes(byte[] buf, int off, int len)
+       {
+               lock (rngMutex) {
+                       Init();
+                       while (len > 0) {
+                               NextBlock();
+                               int clen = Math.Min(len, rblock.Length);
+                               Array.Copy(rblock, 0, buf, off, clen);
+                               off += clen;
+                               len -= clen;
+                       }
+               }
+       }
+
+       /*
+        * Get a new random 32-bit integer (uniform generation).
+        */
+       public static uint U32()
+       {
+               lock (rngMutex) {
+                       Init();
+                       NextBlock();
+                       return (uint)rblock[0]
+                               | ((uint)rblock[1] << 8)
+                               | ((uint)rblock[2] << 16)
+                               | ((uint)rblock[3] << 24);
+               }
+       }
+
+       /*
+        * Convert integer value x (0 to 15) to an hexadecimal character
+        * (lowercase).
+        */
+       static char ToHex(int x)
+       {
+               int hi = -(((x + 6) >> 4) & 1);
+               return (char)(x + 48 + (hi & 39));
+       }
+
+       /*
+        * Get a string of random hexadecimal characters. The 'len'
+        * parameter specifies the string length in characters (it
+        * may be odd).
+        */
+       public static string GetHex(int len)
+       {
+               byte[] buf = new byte[(len + 1) >> 1];
+               GetBytes(buf);
+               StringBuilder sb = new StringBuilder();
+               foreach (byte b in buf) {
+                       sb.Append(b >> 4);
+                       sb.Append(b & 15);
+               }
+               string s = sb.ToString();
+               if (s.Length > len) {
+                       s = s.Substring(0, len);
+               }
+               return s;
+       }
+
+       /*
+        * Get a sequence of random non-zero bytes.
+        */
+       public static void GetBytesNonZero(byte[] buf)
+       {
+               GetBytesNonZero(buf, 0, buf.Length);
+       }
+
+       /*
+        * Get a sequence of random non-zero bytes.
+        */
+       public static void GetBytesNonZero(byte[] buf, int off, int len)
+       {
+               if (len <= 0) {
+                       return;
+               }
+               lock (rngMutex) {
+                       Init();
+                       for (;;) {
+                               NextBlock();
+                               for (int i = 0; i < rblock.Length; i ++) {
+                                       byte x = rblock[i];
+                                       if (x == 0) {
+                                               continue;
+                                       }
+                                       buf[off ++] = x;
+                                       if (-- len == 0) {
+                                               return;
+                                       }
+                               }
+                       }
+               }
+       }
+}
+
+}
diff --git a/Crypto/RSA.cs b/Crypto/RSA.cs
new file mode 100644 (file)
index 0000000..72d95ed
--- /dev/null
@@ -0,0 +1,560 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class implements the RSA encryption and signature algorithms.
+ * The static methods provide access to the algorithm primitives. For
+ * signatures, the hash of the signed data must be provided externally.
+ */
+
+public static class RSA {
+
+       /*
+        * Get the maximum length (in bytes) of an RSA-encrypted value
+        * with the specified public key.
+        */
+       public static int GetMaxEncryptedLength(RSAPublicKey pk)
+       {
+               return pk.Modulus.Length;
+       }
+
+       /*
+        * Encrypt a message with a public key. This applied PKCS#1 v1.5
+        * "type 2" padding. If the message length exceeds the maximum
+        * that can be processed with that public key, an exception is
+        * thrown.
+        *
+        * There are four methods, depending on the kind of source
+        * operand, and how the destination is to be obtained.
+        */
+
+       public static byte[] Encrypt(RSAPublicKey pk, byte[] buf)
+       {
+               return Encrypt(pk, buf, 0, buf.Length);
+       }
+
+       public static byte[] Encrypt(RSAPublicKey pk,
+               byte[] buf, int off, int len)
+       {
+               byte[] n = pk.Modulus;
+               int modLen = n.Length;
+               byte[] x = DoPKCS1Padding(modLen, true, null, buf, off, len);
+               return BigInt.ModPow(x, pk.Exponent, n);
+       }
+
+       public static int Encrypt(RSAPublicKey pk,
+               byte[] buf, byte[] outBuf, int outOff)
+       {
+               return Encrypt(pk, buf, 0, buf.Length, outBuf, outOff);
+       }
+
+       public static int Encrypt(RSAPublicKey pk,
+               byte[] buf, int off, int len, byte[] outBuf, int outOff)
+       {
+               byte[] r = Encrypt(pk, buf, off, len);
+               Array.Copy(r, 0, outBuf, outOff, r.Length);
+               return r.Length;
+       }
+
+       /*
+        * Perform a RSA decryption. A PKCS#1 v1.5 "type 2" padding is
+        * expected, and removed. An exception is thrown on any error.
+        *
+        * WARNING: potentially vulnerable to Bleichenbacher's attack.
+        * Use with care.
+        *
+        * There are four methods, depending on input and output
+        * operands.
+        */
+       public static byte[] Decrypt(RSAPrivateKey sk, byte[] buf)
+       {
+               return Decrypt(sk, buf, 0, buf.Length);
+       }
+
+       public static byte[] Decrypt(RSAPrivateKey sk,
+               byte[] buf, int off, int len)
+       {
+               byte[] tmp = new byte[sk.N.Length];
+               int outLen = Decrypt(sk, buf, off, len, tmp, 0);
+               byte[] outBuf = new byte[outLen];
+               Array.Copy(tmp, 0, outBuf, 0, outLen);
+               return outBuf;
+       }
+
+       public static int Decrypt(RSAPrivateKey sk,
+               byte[] buf, byte[] outBuf, int outOff)
+       {
+               return Decrypt(sk, buf, 0, buf.Length, outBuf, outOff);
+       }
+
+       public static int Decrypt(RSAPrivateKey sk,
+               byte[] buf, int off, int len, byte[] outBuf, int outOff)
+       {
+               if (len != sk.N.Length) {
+                       throw new CryptoException(
+                               "Invalid RSA-encrypted message length");
+               }
+
+               /*
+                * Note: since RSAPrivateKey refuses a modulus of less
+                * than 64 bytes, we know that len >= 64 here.
+                */
+               byte[] x = new byte[len];
+               Array.Copy(buf, off, x, 0, len);
+               DoPrivate(sk, x);
+               if (x[0] != 0x00 || x[1] != 0x02) {
+                       throw new CryptoException(
+                               "Invalid PKCS#1 v1.5 encryption padding");
+               }
+               int i;
+               for (i = 2; i < len && x[i] != 0x00; i ++);
+               if (i < 10 || i >= len) {
+                       throw new CryptoException(
+                               "Invalid PKCS#1 v1.5 encryption padding");
+               }
+               i ++;
+               int olen = len - i;
+               Array.Copy(x, i, outBuf, outOff, olen);
+               return olen;
+       }
+
+       /*
+        * Perform a RSA private key operation (modular exponentiation
+        * with the private exponent). The source array MUST have the
+        * same length as the modulus, and it is modified "in place".
+        *
+        * This function is constant-time, except if the source x[] does
+        * not have the proper length (it should be identical to the
+        * modulus length). If the source array has the proper length
+        * but the numerical value is not in the proper range, then it
+        * is first reduced modulo N.
+        */
+       public static void DoPrivate(RSAPrivateKey sk, byte[] x)
+       {
+               DoPrivate(sk, x, 0, x.Length);
+       }
+
+       public static void DoPrivate(RSAPrivateKey sk,
+               byte[] x, int off, int len)
+       {
+               /*
+                * Check that the source array has the proper length
+                * (identical to the length of the modulus).
+                */
+               if (len != sk.N.Length) {
+                       throw new CryptoException(
+                               "Invalid source length for RSA private");
+               }
+
+               /*
+                * Reduce the source value to the proper range.
+                */
+               ModInt mx = new ModInt(sk.N);
+               mx.DecodeReduce(x, off, len);
+
+               /*
+                * Compute m1 = x^dp mod p.
+                */
+               ModInt m1 = new ModInt(sk.P);
+               m1.Set(mx);
+               m1.Pow(sk.DP);
+
+               /*
+                * Compute m2 = x^dq mod q.
+                */
+               ModInt m2 = new ModInt(sk.Q);
+               m2.Set(mx);
+               m2.Pow(sk.DQ);
+
+               /*
+                * Compute h = (m1 - m2) / q mod p.
+                * (Result goes in m1.)
+                */
+               ModInt m3 = m1.Dup();
+               m3.Set(m2);
+               m1.Sub(m3);
+               m3.Decode(sk.IQ);
+               m1.ToMonty();
+               m1.MontyMul(m3);
+
+               /*
+                * Compute m_2 + q*h. This works on plain integers, but
+                * we have efficient and constant-time code for modular
+                * integers, so we will do it modulo n.
+                */
+               m3 = mx;
+               m3.Set(m1);
+               m1 = m3.Dup();
+               m1.Decode(sk.Q);
+               m1.ToMonty();
+               m3.MontyMul(m1);
+               m1.Set(m2);
+               m3.Add(m1);
+
+               /*
+                * Write result back in x[].
+                */
+               m3.Encode(x, off, len);
+       }
+
+       /*
+        * Constant headers for PKCS#1 v1.5 "type 1" padding. There are
+        * two versions for each header, because of the PKCS#1 ambiguity
+        * with regards to hash function parameters (ASN.1 NULL value,
+        * or omitted).
+        */
+
+       // PKCS#1 with no explicit digest function (special for SSL/TLS)
+       public static byte[] PKCS1_ND = new byte[] { };
+
+       // PKCS#1 with MD5
+       public static byte[] PKCS1_MD5 = new byte[] {
+               0x30, 0x20, 0x30, 0x0C, 0x06, 0x08, 0x2A, 0x86,
+               0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, 0x05, 0x00,
+               0x04, 0x10
+       };
+
+       // PKCS#1 with MD5 (alt)
+       public static byte[] PKCS1_MD5_ALT = new byte[] {
+               0x30, 0x1E, 0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86,
+               0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, 0x04, 0x10
+       };
+
+       // PKCS#1 with SHA-1
+       public static byte[] PKCS1_SHA1 = new byte[] {
+               0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E,
+               0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14
+       };
+
+       // PKCS#1 with SHA-1 (alt)
+       public static byte[] PKCS1_SHA1_ALT = new byte[] {
+               0x30, 0x1F, 0x30, 0x07, 0x06, 0x05, 0x2B, 0x0E,
+               0x03, 0x02, 0x1A, 0x04, 0x14
+       };
+
+       // PKCS#1 with SHA-224
+       public static byte[] PKCS1_SHA224 = new byte[] {
+               0x30, 0x2D, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86,
+               0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05,
+               0x00, 0x04, 0x1C
+       };
+
+       // PKCS#1 with SHA-224 (alt)
+       public static byte[] PKCS1_SHA224_ALT = new byte[] {
+               0x30, 0x2B, 0x30, 0x0B, 0x06, 0x09, 0x60, 0x86,
+               0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x04,
+               0x1C
+       };
+
+       // PKCS#1 with SHA-256
+       public static byte[] PKCS1_SHA256 = new byte[] {
+               0x30, 0x31, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86,
+               0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
+               0x00, 0x04, 0x20
+       };
+
+       // PKCS#1 with SHA-256 (alt)
+       public static byte[] PKCS1_SHA256_ALT = new byte[] {
+               0x30, 0x2F, 0x30, 0x0B, 0x06, 0x09, 0x60, 0x86,
+               0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x04,
+               0x20
+       };
+
+       // PKCS#1 with SHA-384
+       public static byte[] PKCS1_SHA384 = new byte[] {
+               0x30, 0x41, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86,
+               0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05,
+               0x00, 0x04, 0x30
+       };
+
+       // PKCS#1 with SHA-384 (alt)
+       public static byte[] PKCS1_SHA384_ALT = new byte[] {
+               0x30, 0x3F, 0x30, 0x0B, 0x06, 0x09, 0x60, 0x86,
+               0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x04,
+               0x30
+       };
+
+       // PKCS#1 with SHA-512
+       public static byte[] PKCS1_SHA512 = new byte[] {
+               0x30, 0x51, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86,
+               0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
+               0x00, 0x04, 0x40
+       };
+
+       // PKCS#1 with SHA-512 (alt)
+       public static byte[] PKCS1_SHA512_ALT = new byte[] {
+               0x30, 0x4F, 0x30, 0x0B, 0x06, 0x09, 0x60, 0x86,
+               0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x04,
+               0x40
+       };
+
+       /*
+        * Verify an "ND" signature (no digest header: this is for
+        * signatures in SSL/TLS up to TLS-1.1). The hash value
+        * (normally the 36-byte concatenation of MD5 and SHA-1 in
+        * the case of SSL/TLS) and the signature are provided.
+        * Returned value is true on success, false otherwise. No
+        * exception is thrown even if the public key is invalid.
+        */
+
+       public static bool VerifyND(RSAPublicKey pk,
+               byte[] hash, byte[] sig)
+       {
+               return Verify(pk, PKCS1_ND, null,
+                       hash, 0, hash.Length,
+                       sig, 0, sig.Length);
+       }
+
+       public static bool VerifyND(RSAPublicKey pk,
+               byte[] hash, int hashOff, int hashLen, byte[] sig)
+       {
+               return Verify(pk, PKCS1_ND, null,
+                       hash, hashOff, hashLen,
+                       sig, 0, sig.Length);
+       }
+
+       public static bool VerifyND(RSAPublicKey pk,
+               byte[] hash, byte[] sig, int sigOff, int sigLen)
+       {
+               return Verify(pk, PKCS1_ND, null,
+                       hash, 0, hash.Length,
+                       sig, sigOff, sigLen);
+       }
+
+       public static bool VerifyND(RSAPublicKey pk,
+               byte[] hash, int hashOff, int hashLen,
+               byte[] sig, int sigOff, int sigLen)
+       {
+               return Verify(pk, PKCS1_ND, null,
+                       hash, hashOff, hashLen,
+                       sig, sigOff, sigLen);
+       }
+
+       /*
+        * Verify a RSA signature, with PKCS#1 v1.5 "type 1" padding.
+        * The digest header, digest alternative header, hashed data,
+        * and signature value are provided. On any error (including
+        * an invalid RSA public key), false is returned.
+        *
+        * If 'headerAlt' is null, then the signature MUST use the
+        * header value provided in 'header'. Otherwise, the signature
+        * MUST use either 'header' or 'headerAlt'.
+        */
+
+       public static bool Verify(RSAPublicKey pk,
+               byte[] header, byte[] headerAlt,
+               byte[] hash, byte[] sig)
+       {
+               return Verify(pk, header, headerAlt,
+                       hash, 0, hash.Length,
+                       sig, 0, sig.Length);
+       }
+
+       public static bool Verify(RSAPublicKey pk,
+               byte[] header, byte[] headerAlt,
+               byte[] hash, int hashOff, int hashLen, byte[] sig)
+       {
+               return Verify(pk, header, headerAlt,
+                       hash, hashOff, hashLen,
+                       sig, 0, sig.Length);
+       }
+
+       public static bool Verify(RSAPublicKey pk,
+               byte[] header, byte[] headerAlt,
+               byte[] hash, byte[] sig, int sigOff, int sigLen)
+       {
+               return Verify(pk, header, headerAlt,
+                       hash, 0, hash.Length,
+                       sig, sigOff, sigLen);
+       }
+
+       public static bool Verify(RSAPublicKey pk,
+               byte[] header, byte[] headerAlt,
+               byte[] hash, int hashOff, int hashLen,
+               byte[] sig, int sigOff, int sigLen)
+       {
+               /*
+                * Signature must be an integer less than the modulus,
+                * but encoded over exactly the same size as the modulus.
+                */
+               byte[] n = pk.Modulus;
+               int modLen = n.Length;
+               if (sigLen != modLen) {
+                       return false;
+               }
+               byte[] x = new byte[modLen];
+               Array.Copy(sig, sigOff, x, 0, modLen);
+               if (BigInt.Compare(x, n) >= 0) {
+                       return false;
+               }
+
+               /*
+                * Do the RSA exponentation, then verify and remove the
+                * "Type 1" padding (00 01 FF...FF 00 with at least
+                * eight bytes of value FF).
+                */
+               x = BigInt.ModPow(x, pk.Exponent, n);
+               if (x.Length < 11 || x[0] != 0x00 || x[1] != 0x01) {
+                       return false;
+               }
+               int k = 2;
+               while (k < x.Length && x[k] == 0xFF) {
+                       k ++;
+               }
+               if (k < 10 || k == x.Length || x[k] != 0x00) {
+                       return false;
+               }
+               k ++;
+
+               /*
+                * Check that the remaining byte end with the provided
+                * hash value.
+                */
+               int len = modLen - k;
+               if (len < hashLen) {
+                       return false;
+               }
+               for (int i = 0; i < hashLen; i ++) {
+                       if (x[modLen - hashLen + i] != hash[hashOff + i]) {
+                               return false;
+                       }
+               }
+               len -= hashLen;
+
+               /*
+                * Header is at offset 'k', and length 'len'. Compare
+                * with the provided header(s).
+                */
+               if (Eq(header, 0, header.Length, x, k, len)) {
+                       return true;
+               }
+               if (headerAlt != null) {
+                       if (Eq(headerAlt, 0, headerAlt.Length, x, k, len)) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+
+       /*
+        * Compute a RSA signature (PKCS#1 v1.5 "type 1" padding).
+        * The digest header and the hashed data are provided. The
+        * header should be one of the standard PKCS#1 header; it
+        * may also be an empty array or null for a "ND" signature
+        * (this is normally used only in SSL/TLS up to TLS-1.1).
+        */
+
+       public static byte[] Sign(RSAPrivateKey sk, byte[] header,
+               byte[] hash)
+       {
+               return Sign(sk, header, hash, 0, hash.Length);
+       }
+
+       public static byte[] Sign(RSAPrivateKey sk, byte[] header,
+               byte[] hash, int hashOff, int hashLen)
+       {
+               byte[] sig = new byte[sk.N.Length];
+               Sign(sk, header, hash, hashOff, hashLen, sig, 0);
+               return sig;
+       }
+
+       public static int Sign(RSAPrivateKey sk, byte[] header,
+               byte[] hash, byte[] outBuf, int outOff)
+       {
+               return Sign(sk, header, hash, 0, hash.Length, outBuf, outOff);
+       }
+
+       public static int Sign(RSAPrivateKey sk, byte[] header,
+               byte[] hash, int hashOff, int hashLen,
+               byte[] outBuf, int outOff)
+       {
+               int modLen = sk.N.Length;
+               byte[] x = DoPKCS1Padding(modLen, false,
+                       header, hash, hashOff, hashLen);
+               DoPrivate(sk, x);
+               Array.Copy(x, 0, outBuf, outOff, x.Length);
+               return x.Length;
+       }
+
+       /*
+        * Apply PKCS#1 v1.5 padding. The data to pad is the concatenation
+        * of head[] and the chunk of val[] beginning at valOff and of
+        * length valLen. If 'head' is null, then an empty array is used.
+        *
+        * The returned array has length modLen (the modulus size, in bytes).
+        * Padding type 2 (random bytes, for encryption) is used if type2
+        * is true; otherwise, padding type 1 is applied (bytes 0xFF, for
+        * signatures).
+        */
+       public static byte[] DoPKCS1Padding(int modLen, bool type2,
+               byte[] head, byte[] val, int valOff, int valLen)
+       {
+               if (head == null) {
+                       head = PKCS1_ND;
+               }
+               int len = head.Length + valLen;
+               int padLen = modLen - len - 3;
+               if (padLen < 8) {
+                       throw new Exception(
+                               "modulus too short for PKCS#1 padding");
+               }
+               byte[] x = new byte[modLen];
+               x[0] = 0x00;
+               x[1] = (byte)(type2 ? 0x02 : 0x01);
+               if (type2) {
+                       RNG.GetBytesNonZero(x, 2, padLen);
+               } else {
+                       for (int i = 0; i < padLen; i ++) {
+                               x[i + 2] = 0xFF;
+                       }
+               }
+               x[padLen + 2] = 0x00;
+               Array.Copy(head, 0, x, padLen + 3, head.Length);
+               Array.Copy(val, valOff, x, modLen - valLen, valLen);
+               return x;
+       }
+
+       /*
+        * Compare two byte chunks for equality.
+        */
+       static bool Eq(byte[] a, int aoff, int alen,
+               byte[] b, int boff, int blen)
+       {
+               if (alen != blen) {
+                       return false;
+               }
+               for (int i = 0; i < alen; i ++) {
+                       if (a[aoff + i] != b[boff + i]) {
+                               return false;
+                       }
+               }
+               return true;
+       }
+}
+
+}
diff --git a/Crypto/RSAPrivateKey.cs b/Crypto/RSAPrivateKey.cs
new file mode 100644 (file)
index 0000000..fea0a1a
--- /dev/null
@@ -0,0 +1,312 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class contains a RSA private key. The private key elements can
+ * be exported as arrays of bytes (minimal unsigned big-endian
+ * representation).
+ *
+ * Private key elements are:
+ *   n    modulus
+ *   e    public exponent
+ *   d    private exponent
+ *   p    first modulus factor
+ *   q    second modulus factor
+ *   dp   d mod (p-1)
+ *   dq   d mod (q-1)
+ *   iq   (1/q) mod p
+ */
+
+public class RSAPrivateKey : IPrivateKey {
+
+       public byte[] N {
+               get {
+                       return n;
+               }
+       }
+
+       public byte[] E {
+               get {
+                       return e;
+               }
+       }
+
+       public byte[] D {
+               get {
+                       return d;
+               }
+       }
+
+       public byte[] P {
+               get {
+                       return p;
+               }
+       }
+
+       public byte[] Q {
+               get {
+                       return q;
+               }
+       }
+
+       public byte[] DP {
+               get {
+                       return dp;
+               }
+       }
+
+       public byte[] DQ {
+               get {
+                       return dq;
+               }
+       }
+
+       public byte[] IQ {
+               get {
+                       return iq;
+               }
+       }
+
+       public int KeySizeBits {
+               get {
+                       return ((n.Length - 1) << 3)
+                               + BigInt.BitLength(n[0]);
+               }
+       }
+
+       public string AlgorithmName {
+               get {
+                       return "RSA";
+               }
+       }
+
+       IPublicKey IPrivateKey.PublicKey {
+               get {
+                       return this.PublicKey;
+               }
+       }
+
+       public RSAPublicKey PublicKey {
+               get {
+                       return new RSAPublicKey(n, e);
+               }
+       }
+
+       byte[] n, e, d, p, q, dp, dq, iq;
+
+       /*
+        * Create a new instance with the provided elements. Values are
+        * in unsigned big-endian representation.
+        *
+        *   n    modulus
+        *   e    public exponent
+        *   d    private exponent
+        *   p    first modulus factor
+        *   q    second modulus factor
+        *   dp   d mod (p-1)
+        *   dq   d mod (q-1)
+        *   iq   (1/q) mod p
+        *
+        * Rules verified by this constructor:
+        *   n must be odd and at least 512 bits
+        *   e must be odd
+        *   p must be odd
+        *   q must be odd
+        *   p and q are greater than 1
+        *   n is equal to p*q
+        *   dp must be non-zero and lower than p-1
+        *   dq must be non-zero and lower than q-1
+        *   iq must be non-zero and lower than p
+        *
+        * This constructor does NOT verify that:
+        *   p and q are prime
+        *   d is equal to dp modulo p-1
+        *   d is equal to dq modulo q-1
+        *   dp is the inverse of e modulo p-1
+        *   dq is the inverse of e modulo q-1
+        *   iq is the inverse of q modulo p
+        */
+       public RSAPrivateKey(byte[] n, byte[] e, byte[] d,
+               byte[] p, byte[] q, byte[] dp, byte[] dq, byte[] iq)
+       {
+               n = BigInt.NormalizeBE(n);
+               e = BigInt.NormalizeBE(e);
+               d = BigInt.NormalizeBE(d);
+               p = BigInt.NormalizeBE(p);
+               q = BigInt.NormalizeBE(q);
+               dp = BigInt.NormalizeBE(dp);
+               dq = BigInt.NormalizeBE(dq);
+               iq = BigInt.NormalizeBE(iq);
+
+               if (n.Length < 64 || (n.Length == 64 && n[0] < 0x80)) {
+                       throw new CryptoException(
+                               "Invalid RSA private key (less than 512 bits)");
+               }
+               if (!BigInt.IsOdd(n)) {
+                       throw new CryptoException(
+                               "Invalid RSA private key (even modulus)");
+               }
+               if (!BigInt.IsOdd(e)) {
+                       throw new CryptoException(
+                               "Invalid RSA private key (even exponent)");
+               }
+               if (!BigInt.IsOdd(p) || !BigInt.IsOdd(q)) {
+                       throw new CryptoException(
+                               "Invalid RSA private key (even factor)");
+               }
+               if (BigInt.IsOne(p) || BigInt.IsOne(q)) {
+                       throw new CryptoException(
+                               "Invalid RSA private key (trivial factor)");
+               }
+               if (BigInt.Compare(n, BigInt.Mul(p, q)) != 0) {
+                       throw new CryptoException(
+                               "Invalid RSA private key (bad factors)");
+               }
+               if (dp.Length == 0 || dq.Length == 0) {
+                       throw new CryptoException(
+                               "Invalid RSA private key"
+                               + " (null reduced private exponent)");
+               }
+
+               /*
+                * We can temporarily modify p[] and q[] (to compute
+                * p-1 and q-1) since these are freshly produced copies.
+                */
+               p[p.Length - 1] --;
+               q[q.Length - 1] --;
+               if (BigInt.Compare(dp, p) >= 0 || BigInt.Compare(dq, q) >= 0) {
+                       throw new CryptoException(
+                               "Invalid RSA private key"
+                               + " (oversized reduced private exponent)");
+               }
+               p[p.Length - 1] ++;
+               q[q.Length - 1] ++;
+               if (iq.Length == 0 || BigInt.Compare(iq, p) >= 0) {
+                       throw new CryptoException(
+                               "Invalid RSA private key"
+                               + " (out of range CRT coefficient)");
+               }
+               this.n = n;
+               this.e = e;
+               this.d = d;
+               this.p = p;
+               this.q = q;
+               this.dp = dp;
+               this.dq = dq;
+               this.iq = iq;
+       }
+
+       /*
+        * Create a new instance with the provided elements: the two
+        * factors, and the public exponent. The other elements are
+        * computed. Values are in unsigned big-endian representation.
+        * Rules verified by this constructor:
+        *   p must be odd
+        *   q must be odd
+        *   e must be relatively prime to both p-1 and q-1
+        *   e must be greater than 1
+        *   p*q must have size at least 512 bits
+        * TODO: not implemented yet.
+        */
+       public RSAPrivateKey(byte[] p, byte[] q, byte[] e)
+       {
+               throw new Exception("NYI");
+       }
+
+       /*
+        * Create a new instance with the provided elements: the two
+        * factors, and the public exponent. The other elements are
+        * computed. The factors are in unsigned big-endian
+        * representation; the public exponent is a small integer.
+        * Rules verified by this constructor:
+        *   p must be odd
+        *   q must be odd
+        *   e must be relatively prime to both p-1 and q-1
+        *   p*q must have size at least 512 bits
+        * TODO: not implemented yet.
+        */
+       public RSAPrivateKey(byte[] p, byte[] q, uint e)
+               : this(p, q, ToBytes(e))
+       {
+       }
+
+       static byte[] ToBytes(uint x)
+       {
+               byte[] r = new byte[4];
+               r[0] = (byte)(x >> 24);
+               r[1] = (byte)(x >> 16);
+               r[2] = (byte)(x >> 8);
+               r[3] = (byte)x;
+               return r;
+       }
+
+       /*
+        * CheckValid() will verify that the prime factors are indeed
+        * prime, and that all other values are correct.
+        */
+       public void CheckValid()
+       {
+               /*
+                * Factors ought to be prime.
+                */
+               if (!BigInt.IsPrime(p) || !BigInt.IsPrime(q)) {
+                       throw new CryptoException("Invalid RSA private key"
+                               + " (non-prime factor)");
+               }
+
+               /*
+                * FIXME: Verify that:
+                *   dp = d mod p-1
+                *   e*dp = 1 mod p-1
+                *   dq = d mod q-1
+                *   e*dq = 1 mod q-1
+                * (This is not easy with existing code because p-1 and q-1
+                * are even, but ModInt tolerates only odd moduli.)
+                *
+               CheckExp(p, d, dp, e);
+               CheckExp(q, d, dq, e);
+                */
+
+               /*
+                * Verify that:
+                *   q*iq = 1 mod p
+                */
+               ModInt x = new ModInt(p);
+               ModInt y = x.Dup();
+               x.DecodeReduce(q);
+               x.ToMonty();
+               y.Decode(iq);
+               x.MontyMul(y);
+               if (!x.IsOne) {
+                       throw new CryptoException("Invalid RSA private key"
+                               + " (wrong CRT coefficient)");
+               }
+       }
+}
+
+}
diff --git a/Crypto/RSAPublicKey.cs b/Crypto/RSAPublicKey.cs
new file mode 100644 (file)
index 0000000..315a7a3
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * This class contains a RSA public key, defined as a modulus and an
+ * exponent. Both use big-endian representation. This class normalizes
+ * the parameters provided to the constructor so that modulus and
+ * exponent use their minimal unsigned big-endian representation.
+ *
+ * Modulus must have length at least 512 bits. Modulus and exponent
+ * must be odd integers.
+ */
+
+public class RSAPublicKey : IPublicKey {
+
+       public byte[] Modulus {
+               get {
+                       return mod;
+               }
+       }
+
+       public byte[] Exponent {
+               get {
+                       return e;
+               }
+       }
+
+       public int KeySizeBits {
+               get {
+                       return ((mod.Length - 1) << 3)
+                               + BigInt.BitLength(mod[0]);
+               }
+       }
+
+       public string AlgorithmName {
+               get {
+                       return "RSA";
+               }
+       }
+
+       byte[] mod;
+       byte[] e;
+       int hashCode;
+
+       /*
+        * Create a new instance with the provided element (unsigned,
+        * big-endian). This constructor checks the following rules:
+        *
+        *   the modulus size must be at least 512 bits
+        *   the modulus must be odd
+        *   the exponent must be odd and greater than 1
+        */
+       public RSAPublicKey(byte[] modulus, byte[] exponent)
+       {
+               mod = BigInt.NormalizeBE(modulus, false);
+               e = BigInt.NormalizeBE(exponent, false);
+               if (mod.Length < 64 || (mod.Length == 64 && mod[0] < 0x80)) {
+                       throw new CryptoException(
+                               "Invalid RSA public key (less than 512 bits)");
+               }
+               if ((mod[mod.Length - 1] & 0x01) == 0) {
+                       throw new CryptoException(
+                               "Invalid RSA public key (even modulus)");
+               }
+               if (BigInt.IsZero(e)) {
+                       throw new CryptoException(
+                               "Invalid RSA public key (exponent is zero)");
+               }
+               if (BigInt.IsOne(e)) {
+                       throw new CryptoException(
+                               "Invalid RSA public key (exponent is one)");
+               }
+               if ((e[e.Length - 1] & 0x01) == 0) {
+                       throw new CryptoException(
+                               "Invalid RSA public key (even exponent)");
+               }
+
+               /*
+                * A simple hash code that will work well because RSA
+                * keys are in practice quite randomish.
+                */
+               hashCode = (int)(BigInt.HashInt(modulus)
+                       ^ BigInt.HashInt(exponent));
+       }
+
+       /*
+        * For a RSA public key, we cannot, in all generality, check
+        * any more things than we already did in the constructor.
+        * Notably, we cannot check whether the public exponent (e)
+        * is indeed relatively prime to phi(n) (the order of the
+        * invertible group modulo n).
+        */
+       public void CheckValid()
+       {
+               /*
+                * We cannot check more than what we already checked in
+                * the constructor.
+                */
+       }
+
+       public override bool Equals(object obj)
+       {
+               RSAPublicKey p = obj as RSAPublicKey;
+               if (p == null) {
+                       return false;
+               }
+               return BigInt.Compare(mod, p.mod) == 0
+                       && BigInt.Compare(e, p.e) == 0;
+       }
+
+       public override int GetHashCode()
+       {
+               return hashCode;
+       }
+}
+
+}
diff --git a/Crypto/SHA1.cs b/Crypto/SHA1.cs
new file mode 100644 (file)
index 0000000..7ba7bce
--- /dev/null
@@ -0,0 +1,559 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * SHA-1 implementation. SHA-1 is described in FIPS 180-4. Note that
+ * SHA-1 collisions can be computed more efficiently than what would be
+ * expected from an ideal hash function with the same output size; and
+ * this was actually done at least once. Use with care.
+ */
+
+public sealed class SHA1 : DigestCore {
+
+       const int BLOCK_LEN = 64;
+
+       uint A, B, C, D, E;
+       byte[] block, saveBlock;
+       int ptr;
+       ulong byteCount;
+
+       /* 
+        * Create a new instance. It is ready to process data bytes.
+        */
+       public SHA1()
+       {
+               block = new byte[BLOCK_LEN];
+               saveBlock = new byte[BLOCK_LEN];
+               Reset();
+       }
+
+       /* see IDigest */
+       public override string Name {
+               get {
+                       return "SHA-1";
+               }
+       }
+
+       /* see IDigest */
+       public override int DigestSize {
+               get {
+                       return 20;
+               }
+       }
+
+       /* see IDigest */
+       public override int BlockSize {
+               get {
+                       return 64;
+               }
+       }
+
+       /* see IDigest */
+       public override void Reset()
+       {
+               A = 0x67452301;
+               B = 0xEFCDAB89;
+               C = 0x98BADCFE;
+               D = 0x10325476;
+               E = 0xC3D2E1F0;
+               byteCount = 0;
+               ptr = 0;
+       }
+
+       /* see IDigest */
+       public override void Update(byte b)
+       {
+               block[ptr ++] = b;
+               byteCount ++;
+               if (ptr == BLOCK_LEN) {
+                       ProcessBlock();
+               }
+       }
+
+       /* see IDigest */
+       public override void Update(byte[] buf, int off, int len)
+       {
+               if (len < 0) {
+                       throw new ArgumentException("negative chunk length");
+               }
+               byteCount += (ulong)len;
+               while (len > 0) {
+                       int clen = Math.Min(len, BLOCK_LEN - ptr);
+                       Array.Copy(buf, off, block, ptr, clen);
+                       off += clen;
+                       len -= clen;
+                       ptr += clen;
+                       if (ptr == BLOCK_LEN) {
+                               ProcessBlock();
+                       }
+               }
+       }
+
+       /* see IDigest */
+       public override void DoPartial(byte[] outBuf, int off)
+       {
+               /*
+                * Save current state.
+                */
+               uint saveA = A;
+               uint saveB = B;
+               uint saveC = C;
+               uint saveD = D;
+               uint saveE = E;
+               int savePtr = ptr;
+               Array.Copy(block, 0, saveBlock, 0, savePtr);
+
+               /*
+                * Add padding. This may involve processing an extra block.
+                */
+               block[ptr ++] = 0x80;
+               if (ptr > BLOCK_LEN - 8) {
+                       for (int j = ptr; j < BLOCK_LEN; j ++) {
+                               block[j] = 0;
+                       }
+                       ProcessBlock();
+               }
+               for (int j = ptr; j < (BLOCK_LEN - 8); j ++) {
+                       block[j] = 0;
+               }
+               ulong x = byteCount << 3;
+               Enc32be((uint)(x >> 32), block, BLOCK_LEN - 8);
+               Enc32be((uint)x, block, BLOCK_LEN - 4);
+
+               /*
+                * Process final block and encode result.
+                */
+               ProcessBlock();
+               Enc32be(A, outBuf, off);
+               Enc32be(B, outBuf, off + 4);
+               Enc32be(C, outBuf, off + 8);
+               Enc32be(D, outBuf, off + 12);
+               Enc32be(E, outBuf, off + 16);
+
+               /*
+                * Restore current state.
+                */
+               Array.Copy(saveBlock, 0, block, 0, savePtr);
+               A = saveA;
+               B = saveB;
+               C = saveC;
+               D = saveD;
+               E = saveE;
+               ptr = savePtr;
+       }
+
+       /* see IDigest */
+       public override IDigest Dup()
+       {
+               SHA1 h = new SHA1();
+               h.A = A;
+               h.B = B;
+               h.C = C;
+               h.D = D;
+               h.E = E;
+               h.ptr = ptr;
+               h.byteCount = byteCount;
+               Array.Copy(block, 0, h.block, 0, ptr);
+               return h;
+       }
+
+       /* see IDigest */
+       public override void CurrentState(byte[] outBuf, int off)
+       {
+               Enc32be(A, outBuf, off);
+               Enc32be(B, outBuf, off + 4);
+               Enc32be(C, outBuf, off + 8);
+               Enc32be(D, outBuf, off + 12);
+               Enc32be(E, outBuf, off + 16);
+       }
+
+       const uint K1 = 0x5A827999;
+       const uint K2 = 0x6ED9EBA1;
+       const uint K3 = 0x8F1BBCDC;
+       const uint K4 = 0xCA62C1D6;
+
+       void ProcessBlock()
+       {
+               /*
+                * Read state words.
+                */
+               uint wa = A;
+               uint wb = B;
+               uint wc = C;
+               uint wd = D;
+               uint we = E;
+
+               /*
+                * Rounds 0 to 19.
+                */
+               uint x0 = Dec32be(block,  0);
+               we += ((wa << 5) | (wa >> 27)) + (wd ^ (wb & (wc ^ wd))) + x0 + K1;
+               wb = (wb << 30) | (wb >> 2);
+               uint x1 = Dec32be(block,  4);
+               wd += ((we << 5) | (we >> 27)) + (wc ^ (wa & (wb ^ wc))) + x1 + K1;
+               wa = (wa << 30) | (wa >> 2);
+               uint x2 = Dec32be(block,  8);
+               wc += ((wd << 5) | (wd >> 27)) + (wb ^ (we & (wa ^ wb))) + x2 + K1;
+               we = (we << 30) | (we >> 2);
+               uint x3 = Dec32be(block, 12);
+               wb += ((wc << 5) | (wc >> 27)) + (wa ^ (wd & (we ^ wa))) + x3 + K1;
+               wd = (wd << 30) | (wd >> 2);
+               uint x4 = Dec32be(block, 16);
+               wa += ((wb << 5) | (wb >> 27)) + (we ^ (wc & (wd ^ we))) + x4 + K1;
+               wc = (wc << 30) | (wc >> 2);
+               uint x5 = Dec32be(block, 20);
+               we += ((wa << 5) | (wa >> 27)) + (wd ^ (wb & (wc ^ wd))) + x5 + K1;
+               wb = (wb << 30) | (wb >> 2);
+               uint x6 = Dec32be(block, 24);
+               wd += ((we << 5) | (we >> 27)) + (wc ^ (wa & (wb ^ wc))) + x6 + K1;
+               wa = (wa << 30) | (wa >> 2);
+               uint x7 = Dec32be(block, 28);
+               wc += ((wd << 5) | (wd >> 27)) + (wb ^ (we & (wa ^ wb))) + x7 + K1;
+               we = (we << 30) | (we >> 2);
+               uint x8 = Dec32be(block, 32);
+               wb += ((wc << 5) | (wc >> 27)) + (wa ^ (wd & (we ^ wa))) + x8 + K1;
+               wd = (wd << 30) | (wd >> 2);
+               uint x9 = Dec32be(block, 36);
+               wa += ((wb << 5) | (wb >> 27)) + (we ^ (wc & (wd ^ we))) + x9 + K1;
+               wc = (wc << 30) | (wc >> 2);
+               uint xA = Dec32be(block, 40);
+               we += ((wa << 5) | (wa >> 27)) + (wd ^ (wb & (wc ^ wd))) + xA + K1;
+               wb = (wb << 30) | (wb >> 2);
+               uint xB = Dec32be(block, 44);
+               wd += ((we << 5) | (we >> 27)) + (wc ^ (wa & (wb ^ wc))) + xB + K1;
+               wa = (wa << 30) | (wa >> 2);
+               uint xC = Dec32be(block, 48);
+               wc += ((wd << 5) | (wd >> 27)) + (wb ^ (we & (wa ^ wb))) + xC + K1;
+               we = (we << 30) | (we >> 2);
+               uint xD = Dec32be(block, 52);
+               wb += ((wc << 5) | (wc >> 27)) + (wa ^ (wd & (we ^ wa))) + xD + K1;
+               wd = (wd << 30) | (wd >> 2);
+               uint xE = Dec32be(block, 56);
+               wa += ((wb << 5) | (wb >> 27)) + (we ^ (wc & (wd ^ we))) + xE + K1;
+               wc = (wc << 30) | (wc >> 2);
+               uint xF = Dec32be(block, 60);
+               we += ((wa << 5) | (wa >> 27)) + (wd ^ (wb & (wc ^ wd))) + xF + K1;
+               wb = (wb << 30) | (wb >> 2);
+               x0 ^= xD ^ x8 ^ x2;
+               x0 = (x0 << 1) | (x0 >> 31);
+               wd += ((we << 5) | (we >> 27)) + (wc ^ (wa & (wb ^ wc))) + x0 + K1;
+               wa = (wa << 30) | (wa >> 2);
+               x1 ^= xE ^ x9 ^ x3;
+               x1 = (x1 << 1) | (x1 >> 31);
+               wc += ((wd << 5) | (wd >> 27)) + (wb ^ (we & (wa ^ wb))) + x1 + K1;
+               we = (we << 30) | (we >> 2);
+               x2 ^= xF ^ xA ^ x4;
+               x2 = (x2 << 1) | (x2 >> 31);
+               wb += ((wc << 5) | (wc >> 27)) + (wa ^ (wd & (we ^ wa))) + x2 + K1;
+               wd = (wd << 30) | (wd >> 2);
+               x3 ^= x0 ^ xB ^ x5;
+               x3 = (x3 << 1) | (x3 >> 31);
+               wa += ((wb << 5) | (wb >> 27)) + (we ^ (wc & (wd ^ we))) + x3 + K1;
+               wc = (wc << 30) | (wc >> 2);
+
+               /*
+                * Rounds 20 to 39.
+                */
+               x4 ^= x1 ^ xC ^ x6;
+               x4 = (x4 << 1) | (x4 >> 31);
+               we += ((wa << 5) | (wa >> 27)) + (wb ^ wc ^ wd) + x4 + K2;
+               wb = (wb << 30) | (wb >> 2);
+               x5 ^= x2 ^ xD ^ x7;
+               x5 = (x5 << 1) | (x5 >> 31);
+               wd += ((we << 5) | (we >> 27)) + (wa ^ wb ^ wc) + x5 + K2;
+               wa = (wa << 30) | (wa >> 2);
+               x6 ^= x3 ^ xE ^ x8;
+               x6 = (x6 << 1) | (x6 >> 31);
+               wc += ((wd << 5) | (wd >> 27)) + (we ^ wa ^ wb) + x6 + K2;
+               we = (we << 30) | (we >> 2);
+               x7 ^= x4 ^ xF ^ x9;
+               x7 = (x7 << 1) | (x7 >> 31);
+               wb += ((wc << 5) | (wc >> 27)) + (wd ^ we ^ wa) + x7 + K2;
+               wd = (wd << 30) | (wd >> 2);
+               x8 ^= x5 ^ x0 ^ xA;
+               x8 = (x8 << 1) | (x8 >> 31);
+               wa += ((wb << 5) | (wb >> 27)) + (wc ^ wd ^ we) + x8 + K2;
+               wc = (wc << 30) | (wc >> 2);
+               x9 ^= x6 ^ x1 ^ xB;
+               x9 = (x9 << 1) | (x9 >> 31);
+               we += ((wa << 5) | (wa >> 27)) + (wb ^ wc ^ wd) + x9 + K2;
+               wb = (wb << 30) | (wb >> 2);
+               xA ^= x7 ^ x2 ^ xC;
+               xA = (xA << 1) | (xA >> 31);
+               wd += ((we << 5) | (we >> 27)) + (wa ^ wb ^ wc) + xA + K2;
+               wa = (wa << 30) | (wa >> 2);
+               xB ^= x8 ^ x3 ^ xD;
+               xB = (xB << 1) | (xB >> 31);
+               wc += ((wd << 5) | (wd >> 27)) + (we ^ wa ^ wb) + xB + K2;
+               we = (we << 30) | (we >> 2);
+               xC ^= x9 ^ x4 ^ xE;
+               xC = (xC << 1) | (xC >> 31);
+               wb += ((wc << 5) | (wc >> 27)) + (wd ^ we ^ wa) + xC + K2;
+               wd = (wd << 30) | (wd >> 2);
+               xD ^= xA ^ x5 ^ xF;
+               xD = (xD << 1) | (xD >> 31);
+               wa += ((wb << 5) | (wb >> 27)) + (wc ^ wd ^ we) + xD + K2;
+               wc = (wc << 30) | (wc >> 2);
+               xE ^= xB ^ x6 ^ x0;
+               xE = (xE << 1) | (xE >> 31);
+               we += ((wa << 5) | (wa >> 27)) + (wb ^ wc ^ wd) + xE + K2;
+               wb = (wb << 30) | (wb >> 2);
+               xF ^= xC ^ x7 ^ x1;
+               xF = (xF << 1) | (xF >> 31);
+               wd += ((we << 5) | (we >> 27)) + (wa ^ wb ^ wc) + xF + K2;
+               wa = (wa << 30) | (wa >> 2);
+               x0 ^= xD ^ x8 ^ x2;
+               x0 = (x0 << 1) | (x0 >> 31);
+               wc += ((wd << 5) | (wd >> 27)) + (we ^ wa ^ wb) + x0 + K2;
+               we = (we << 30) | (we >> 2);
+               x1 ^= xE ^ x9 ^ x3;
+               x1 = (x1 << 1) | (x1 >> 31);
+               wb += ((wc << 5) | (wc >> 27)) + (wd ^ we ^ wa) + x1 + K2;
+               wd = (wd << 30) | (wd >> 2);
+               x2 ^= xF ^ xA ^ x4;
+               x2 = (x2 << 1) | (x2 >> 31);
+               wa += ((wb << 5) | (wb >> 27)) + (wc ^ wd ^ we) + x2 + K2;
+               wc = (wc << 30) | (wc >> 2);
+               x3 ^= x0 ^ xB ^ x5;
+               x3 = (x3 << 1) | (x3 >> 31);
+               we += ((wa << 5) | (wa >> 27)) + (wb ^ wc ^ wd) + x3 + K2;
+               wb = (wb << 30) | (wb >> 2);
+               x4 ^= x1 ^ xC ^ x6;
+               x4 = (x4 << 1) | (x4 >> 31);
+               wd += ((we << 5) | (we >> 27)) + (wa ^ wb ^ wc) + x4 + K2;
+               wa = (wa << 30) | (wa >> 2);
+               x5 ^= x2 ^ xD ^ x7;
+               x5 = (x5 << 1) | (x5 >> 31);
+               wc += ((wd << 5) | (wd >> 27)) + (we ^ wa ^ wb) + x5 + K2;
+               we = (we << 30) | (we >> 2);
+               x6 ^= x3 ^ xE ^ x8;
+               x6 = (x6 << 1) | (x6 >> 31);
+               wb += ((wc << 5) | (wc >> 27)) + (wd ^ we ^ wa) + x6 + K2;
+               wd = (wd << 30) | (wd >> 2);
+               x7 ^= x4 ^ xF ^ x9;
+               x7 = (x7 << 1) | (x7 >> 31);
+               wa += ((wb << 5) | (wb >> 27)) + (wc ^ wd ^ we) + x7 + K2;
+               wc = (wc << 30) | (wc >> 2);
+
+               /*
+                * Rounds 40 to 59.
+                */
+               x8 ^= x5 ^ x0 ^ xA;
+               x8 = (x8 << 1) | (x8 >> 31);
+               we += ((wa << 5) | (wa >> 27)) + ((wc & wd) ^ (wb & (wc ^ wd))) + x8 + K3;
+               wb = (wb << 30) | (wb >> 2);
+               x9 ^= x6 ^ x1 ^ xB;
+               x9 = (x9 << 1) | (x9 >> 31);
+               wd += ((we << 5) | (we >> 27)) + ((wb & wc) ^ (wa & (wb ^ wc))) + x9 + K3;
+               wa = (wa << 30) | (wa >> 2);
+               xA ^= x7 ^ x2 ^ xC;
+               xA = (xA << 1) | (xA >> 31);
+               wc += ((wd << 5) | (wd >> 27)) + ((wa & wb) ^ (we & (wa ^ wb))) + xA + K3;
+               we = (we << 30) | (we >> 2);
+               xB ^= x8 ^ x3 ^ xD;
+               xB = (xB << 1) | (xB >> 31);
+               wb += ((wc << 5) | (wc >> 27)) + ((we & wa) ^ (wd & (we ^ wa))) + xB + K3;
+               wd = (wd << 30) | (wd >> 2);
+               xC ^= x9 ^ x4 ^ xE;
+               xC = (xC << 1) | (xC >> 31);
+               wa += ((wb << 5) | (wb >> 27)) + ((wd & we) ^ (wc & (wd ^ we))) + xC + K3;
+               wc = (wc << 30) | (wc >> 2);
+               xD ^= xA ^ x5 ^ xF;
+               xD = (xD << 1) | (xD >> 31);
+               we += ((wa << 5) | (wa >> 27)) + ((wc & wd) ^ (wb & (wc ^ wd))) + xD + K3;
+               wb = (wb << 30) | (wb >> 2);
+               xE ^= xB ^ x6 ^ x0;
+               xE = (xE << 1) | (xE >> 31);
+               wd += ((we << 5) | (we >> 27)) + ((wb & wc) ^ (wa & (wb ^ wc))) + xE + K3;
+               wa = (wa << 30) | (wa >> 2);
+               xF ^= xC ^ x7 ^ x1;
+               xF = (xF << 1) | (xF >> 31);
+               wc += ((wd << 5) | (wd >> 27)) + ((wa & wb) ^ (we & (wa ^ wb))) + xF + K3;
+               we = (we << 30) | (we >> 2);
+               x0 ^= xD ^ x8 ^ x2;
+               x0 = (x0 << 1) | (x0 >> 31);
+               wb += ((wc << 5) | (wc >> 27)) + ((we & wa) ^ (wd & (we ^ wa))) + x0 + K3;
+               wd = (wd << 30) | (wd >> 2);
+               x1 ^= xE ^ x9 ^ x3;
+               x1 = (x1 << 1) | (x1 >> 31);
+               wa += ((wb << 5) | (wb >> 27)) + ((wd & we) ^ (wc & (wd ^ we))) + x1 + K3;
+               wc = (wc << 30) | (wc >> 2);
+               x2 ^= xF ^ xA ^ x4;
+               x2 = (x2 << 1) | (x2 >> 31);
+               we += ((wa << 5) | (wa >> 27)) + ((wc & wd) ^ (wb & (wc ^ wd))) + x2 + K3;
+               wb = (wb << 30) | (wb >> 2);
+               x3 ^= x0 ^ xB ^ x5;
+               x3 = (x3 << 1) | (x3 >> 31);
+               wd += ((we << 5) | (we >> 27)) + ((wb & wc) ^ (wa & (wb ^ wc))) + x3 + K3;
+               wa = (wa << 30) | (wa >> 2);
+               x4 ^= x1 ^ xC ^ x6;
+               x4 = (x4 << 1) | (x4 >> 31);
+               wc += ((wd << 5) | (wd >> 27)) + ((wa & wb) ^ (we & (wa ^ wb))) + x4 + K3;
+               we = (we << 30) | (we >> 2);
+               x5 ^= x2 ^ xD ^ x7;
+               x5 = (x5 << 1) | (x5 >> 31);
+               wb += ((wc << 5) | (wc >> 27)) + ((we & wa) ^ (wd & (we ^ wa))) + x5 + K3;
+               wd = (wd << 30) | (wd >> 2);
+               x6 ^= x3 ^ xE ^ x8;
+               x6 = (x6 << 1) | (x6 >> 31);
+               wa += ((wb << 5) | (wb >> 27)) + ((wd & we) ^ (wc & (wd ^ we))) + x6 + K3;
+               wc = (wc << 30) | (wc >> 2);
+               x7 ^= x4 ^ xF ^ x9;
+               x7 = (x7 << 1) | (x7 >> 31);
+               we += ((wa << 5) | (wa >> 27)) + ((wc & wd) ^ (wb & (wc ^ wd))) + x7 + K3;
+               wb = (wb << 30) | (wb >> 2);
+               x8 ^= x5 ^ x0 ^ xA;
+               x8 = (x8 << 1) | (x8 >> 31);
+               wd += ((we << 5) | (we >> 27)) + ((wb & wc) ^ (wa & (wb ^ wc))) + x8 + K3;
+               wa = (wa << 30) | (wa >> 2);
+               x9 ^= x6 ^ x1 ^ xB;
+               x9 = (x9 << 1) | (x9 >> 31);
+               wc += ((wd << 5) | (wd >> 27)) + ((wa & wb) ^ (we & (wa ^ wb))) + x9 + K3;
+               we = (we << 30) | (we >> 2);
+               xA ^= x7 ^ x2 ^ xC;
+               xA = (xA << 1) | (xA >> 31);
+               wb += ((wc << 5) | (wc >> 27)) + ((we & wa) ^ (wd & (we ^ wa))) + xA + K3;
+               wd = (wd << 30) | (wd >> 2);
+               xB ^= x8 ^ x3 ^ xD;
+               xB = (xB << 1) | (xB >> 31);
+               wa += ((wb << 5) | (wb >> 27)) + ((wd & we) ^ (wc & (wd ^ we))) + xB + K3;
+               wc = (wc << 30) | (wc >> 2);
+
+               /*
+                * Rounds 60 to 79.
+                */
+               xC ^= x9 ^ x4 ^ xE;
+               xC = (xC << 1) | (xC >> 31);
+               we += ((wa << 5) | (wa >> 27)) + (wb ^ wc ^ wd) + xC + K4;
+               wb = (wb << 30) | (wb >> 2);
+               xD ^= xA ^ x5 ^ xF;
+               xD = (xD << 1) | (xD >> 31);
+               wd += ((we << 5) | (we >> 27)) + (wa ^ wb ^ wc) + xD + K4;
+               wa = (wa << 30) | (wa >> 2);
+               xE ^= xB ^ x6 ^ x0;
+               xE = (xE << 1) | (xE >> 31);
+               wc += ((wd << 5) | (wd >> 27)) + (we ^ wa ^ wb) + xE + K4;
+               we = (we << 30) | (we >> 2);
+               xF ^= xC ^ x7 ^ x1;
+               xF = (xF << 1) | (xF >> 31);
+               wb += ((wc << 5) | (wc >> 27)) + (wd ^ we ^ wa) + xF + K4;
+               wd = (wd << 30) | (wd >> 2);
+               x0 ^= xD ^ x8 ^ x2;
+               x0 = (x0 << 1) | (x0 >> 31);
+               wa += ((wb << 5) | (wb >> 27)) + (wc ^ wd ^ we) + x0 + K4;
+               wc = (wc << 30) | (wc >> 2);
+               x1 ^= xE ^ x9 ^ x3;
+               x1 = (x1 << 1) | (x1 >> 31);
+               we += ((wa << 5) | (wa >> 27)) + (wb ^ wc ^ wd) + x1 + K4;
+               wb = (wb << 30) | (wb >> 2);
+               x2 ^= xF ^ xA ^ x4;
+               x2 = (x2 << 1) | (x2 >> 31);
+               wd += ((we << 5) | (we >> 27)) + (wa ^ wb ^ wc) + x2 + K4;
+               wa = (wa << 30) | (wa >> 2);
+               x3 ^= x0 ^ xB ^ x5;
+               x3 = (x3 << 1) | (x3 >> 31);
+               wc += ((wd << 5) | (wd >> 27)) + (we ^ wa ^ wb) + x3 + K4;
+               we = (we << 30) | (we >> 2);
+               x4 ^= x1 ^ xC ^ x6;
+               x4 = (x4 << 1) | (x4 >> 31);
+               wb += ((wc << 5) | (wc >> 27)) + (wd ^ we ^ wa) + x4 + K4;
+               wd = (wd << 30) | (wd >> 2);
+               x5 ^= x2 ^ xD ^ x7;
+               x5 = (x5 << 1) | (x5 >> 31);
+               wa += ((wb << 5) | (wb >> 27)) + (wc ^ wd ^ we) + x5 + K4;
+               wc = (wc << 30) | (wc >> 2);
+               x6 ^= x3 ^ xE ^ x8;
+               x6 = (x6 << 1) | (x6 >> 31);
+               we += ((wa << 5) | (wa >> 27)) + (wb ^ wc ^ wd) + x6 + K4;
+               wb = (wb << 30) | (wb >> 2);
+               x7 ^= x4 ^ xF ^ x9;
+               x7 = (x7 << 1) | (x7 >> 31);
+               wd += ((we << 5) | (we >> 27)) + (wa ^ wb ^ wc) + x7 + K4;
+               wa = (wa << 30) | (wa >> 2);
+               x8 ^= x5 ^ x0 ^ xA;
+               x8 = (x8 << 1) | (x8 >> 31);
+               wc += ((wd << 5) | (wd >> 27)) + (we ^ wa ^ wb) + x8 + K4;
+               we = (we << 30) | (we >> 2);
+               x9 ^= x6 ^ x1 ^ xB;
+               x9 = (x9 << 1) | (x9 >> 31);
+               wb += ((wc << 5) | (wc >> 27)) + (wd ^ we ^ wa) + x9 + K4;
+               wd = (wd << 30) | (wd >> 2);
+               xA ^= x7 ^ x2 ^ xC;
+               xA = (xA << 1) | (xA >> 31);
+               wa += ((wb << 5) | (wb >> 27)) + (wc ^ wd ^ we) + xA + K4;
+               wc = (wc << 30) | (wc >> 2);
+               xB ^= x8 ^ x3 ^ xD;
+               xB = (xB << 1) | (xB >> 31);
+               we += ((wa << 5) | (wa >> 27)) + (wb ^ wc ^ wd) + xB + K4;
+               wb = (wb << 30) | (wb >> 2);
+               xC ^= x9 ^ x4 ^ xE;
+               xC = (xC << 1) | (xC >> 31);
+               wd += ((we << 5) | (we >> 27)) + (wa ^ wb ^ wc) + xC + K4;
+               wa = (wa << 30) | (wa >> 2);
+               xD ^= xA ^ x5 ^ xF;
+               xD = (xD << 1) | (xD >> 31);
+               wc += ((wd << 5) | (wd >> 27)) + (we ^ wa ^ wb) + xD + K4;
+               we = (we << 30) | (we >> 2);
+               xE ^= xB ^ x6 ^ x0;
+               xE = (xE << 1) | (xE >> 31);
+               wb += ((wc << 5) | (wc >> 27)) + (wd ^ we ^ wa) + xE + K4;
+               wd = (wd << 30) | (wd >> 2);
+               xF ^= xC ^ x7 ^ x1;
+               xF = (xF << 1) | (xF >> 31);
+               wa += ((wb << 5) | (wb >> 27)) + (wc ^ wd ^ we) + xF + K4;
+               wc = (wc << 30) | (wc >> 2);
+
+               /*
+                * Update state words and reset block pointer.
+                */
+               A += wa;
+               B += wb;
+               C += wc;
+               D += wd;
+               E += we;
+               ptr = 0;
+       }
+
+       static uint Dec32be(byte[] buf, int off)
+       {
+               return ((uint)buf[off] << 24)
+                       | ((uint)buf[off + 1] << 16)
+                       | ((uint)buf[off + 2] << 8)
+                       | (uint)buf[off + 3];
+       }
+
+       static void Enc32be(uint x, byte[] buf, int off)
+       {
+               buf[off] = (byte)(x >> 24);
+               buf[off + 1] = (byte)(x >> 16);
+               buf[off + 2] = (byte)(x >> 8);
+               buf[off + 3] = (byte)x;
+       }
+}
+
+}
diff --git a/Crypto/SHA224.cs b/Crypto/SHA224.cs
new file mode 100644 (file)
index 0000000..236bbd4
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * SHA-224 implementation. SHA-224 is described in FIPS 180-4.
+ */
+
+public sealed class SHA224 : SHA2Small {
+
+       /*
+        * Create a new instance, ready to process data bytes.
+        */
+       public SHA224()
+       {
+       }
+
+       /* see IDigest */
+       public override string Name {
+               get {
+                       return "SHA-224";
+               }
+       }
+
+       /* see IDigest */
+       public override int DigestSize {
+               get {
+                       return 28;
+               }
+       }
+
+       internal override uint[] IV {
+               get {
+                       return IV224;
+               }
+       }
+
+       internal override SHA2Small DupInner()
+       {
+               return new SHA224();
+       }
+
+       static uint[] IV224 = {
+               0xC1059ED8, 0x367CD507, 0x3070DD17, 0xF70E5939,
+               0xFFC00B31, 0x68581511, 0x64F98FA7, 0xBEFA4FA4
+       };
+}
+
+}
diff --git a/Crypto/SHA256.cs b/Crypto/SHA256.cs
new file mode 100644 (file)
index 0000000..3328eb5
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * SHA-256 implementation. SHA-256 is described in FIPS 180-4.
+ */
+
+public sealed class SHA256 : SHA2Small {
+
+       /*
+        * Create a new instance, ready to process data bytes.
+        */
+       public SHA256()
+       {
+       }
+
+       /* see IDigest */
+       public override string Name {
+               get {
+                       return "SHA-256";
+               }
+       }
+
+       /* see IDigest */
+       public override int DigestSize {
+               get {
+                       return 32;
+               }
+       }
+
+       internal override uint[] IV {
+               get {
+                       return IV256;
+               }
+       }
+
+       internal override SHA2Small DupInner()
+       {
+               return new SHA256();
+       }
+
+       static uint[] IV256 = {
+               0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A,
+               0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19
+       };
+}
+
+}
diff --git a/Crypto/SHA2Big.cs b/Crypto/SHA2Big.cs
new file mode 100644 (file)
index 0000000..0e38ac6
--- /dev/null
@@ -0,0 +1,387 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Implementation of SHA-384 and SHA-512, as described in FIPS 180-4.
+ */
+
+public abstract class SHA2Big : DigestCore {
+
+       const int BLOCK_LEN = 128;
+
+       ulong[] state;
+       byte[] block, saveBlock;
+       int ptr;
+       ulong byteCount;
+       ulong[] W;
+
+       /*
+        * Create a new instance, ready to process data bytes. The
+        * output length (in bytes) and initial value must be specified.
+        */
+       internal SHA2Big()
+       {
+               state = new ulong[8];
+               block = new byte[BLOCK_LEN];
+               saveBlock = new byte[BLOCK_LEN];
+               W = new ulong[80];
+               Reset();
+       }
+
+       internal abstract ulong[] IV { get; }
+
+       internal abstract SHA2Big DupInner();
+
+       /* see IDigest */
+       public override int BlockSize {
+               get {
+                       return BLOCK_LEN;
+               }
+       }
+
+       /* see IDigest */
+       public override void Reset()
+       {
+               Array.Copy(IV, 0, state, 0, state.Length);
+               byteCount = 0;
+               ptr = 0;
+       }
+
+       /* see IDigest */
+       public override void Update(byte b)
+       {
+               block[ptr ++] = b;
+               byteCount ++;
+               if (ptr == BLOCK_LEN) {
+                       ProcessBlock();
+               }
+       }
+
+       /* see IDigest */
+       public override void Update(byte[] buf, int off, int len)
+       {
+               if (len < 0) {
+                       throw new ArgumentException("negative chunk length");
+               }
+               byteCount += (ulong)len;
+               while (len > 0) {
+                       int clen = Math.Min(len, BLOCK_LEN - ptr);
+                       Array.Copy(buf, off, block, ptr, clen);
+                       off += clen;
+                       len -= clen;
+                       ptr += clen;
+                       if (ptr == BLOCK_LEN) {
+                               ProcessBlock();
+                       }
+               }
+       }
+
+       /* see IDigest */
+       public override void DoPartial(byte[] outBuf, int off)
+       {
+               /*
+                * Save current state.
+                */
+               ulong A = state[0];
+               ulong B = state[1];
+               ulong C = state[2];
+               ulong D = state[3];
+               ulong E = state[4];
+               ulong F = state[5];
+               ulong G = state[6];
+               ulong H = state[7];
+               int savePtr = ptr;
+               Array.Copy(block, 0, saveBlock, 0, savePtr);
+
+               /*
+                * Add padding. This may involve processing an extra block.
+                */
+               block[ptr ++] = 0x80;
+               if (ptr > BLOCK_LEN - 16) {
+                       for (int j = ptr; j < BLOCK_LEN; j ++) {
+                               block[j] = 0;
+                       }
+                       ProcessBlock();
+               }
+               for (int j = ptr; j < (BLOCK_LEN - 16); j ++) {
+                       block[j] = 0;
+               }
+               Enc64be(byteCount >> 61, block, BLOCK_LEN - 16);
+               Enc64be(byteCount << 3, block, BLOCK_LEN - 8);
+
+               /*
+                * Process final block and encode result.
+                */
+               ProcessBlock();
+               int n = DigestSize >> 3;
+               for (int i = 0; i < n; i ++) {
+                       Enc64be(state[i], outBuf, off + (i << 3));
+               }
+
+               /*
+                * Restore current state.
+                */
+               Array.Copy(saveBlock, 0, block, 0, savePtr);
+               state[0] = A;
+               state[1] = B;
+               state[2] = C;
+               state[3] = D;
+               state[4] = E;
+               state[5] = F;
+               state[6] = G;
+               state[7] = H;
+               ptr = savePtr;
+       }
+
+       /* see IDigest */
+       public override IDigest Dup()
+       {
+               SHA2Big h = DupInner();
+               Array.Copy(state, 0, h.state, 0, state.Length);
+               h.ptr = ptr;
+               h.byteCount = byteCount;
+               Array.Copy(block, 0, h.block, 0, ptr);
+               return h;
+       }
+
+       /* see IDigest */
+       public override void CurrentState(byte[] outBuf, int off)
+       {
+               int n = DigestSize >> 3;
+               for (int i = 0; i < n; i ++) {
+                       Enc64be(state[i], outBuf, off + (i << 3));
+               }
+       }
+
+       static ulong[] K = {
+               0x428A2F98D728AE22, 0x7137449123EF65CD,
+               0xB5C0FBCFEC4D3B2F, 0xE9B5DBA58189DBBC,
+               0x3956C25BF348B538, 0x59F111F1B605D019,
+               0x923F82A4AF194F9B, 0xAB1C5ED5DA6D8118,
+               0xD807AA98A3030242, 0x12835B0145706FBE,
+               0x243185BE4EE4B28C, 0x550C7DC3D5FFB4E2,
+               0x72BE5D74F27B896F, 0x80DEB1FE3B1696B1,
+               0x9BDC06A725C71235, 0xC19BF174CF692694,
+               0xE49B69C19EF14AD2, 0xEFBE4786384F25E3,
+               0x0FC19DC68B8CD5B5, 0x240CA1CC77AC9C65,
+               0x2DE92C6F592B0275, 0x4A7484AA6EA6E483,
+               0x5CB0A9DCBD41FBD4, 0x76F988DA831153B5,
+               0x983E5152EE66DFAB, 0xA831C66D2DB43210,
+               0xB00327C898FB213F, 0xBF597FC7BEEF0EE4,
+               0xC6E00BF33DA88FC2, 0xD5A79147930AA725,
+               0x06CA6351E003826F, 0x142929670A0E6E70,
+               0x27B70A8546D22FFC, 0x2E1B21385C26C926,
+               0x4D2C6DFC5AC42AED, 0x53380D139D95B3DF,
+               0x650A73548BAF63DE, 0x766A0ABB3C77B2A8,
+               0x81C2C92E47EDAEE6, 0x92722C851482353B,
+               0xA2BFE8A14CF10364, 0xA81A664BBC423001,
+               0xC24B8B70D0F89791, 0xC76C51A30654BE30,
+               0xD192E819D6EF5218, 0xD69906245565A910,
+               0xF40E35855771202A, 0x106AA07032BBD1B8,
+               0x19A4C116B8D2D0C8, 0x1E376C085141AB53,
+               0x2748774CDF8EEB99, 0x34B0BCB5E19B48A8,
+               0x391C0CB3C5C95A63, 0x4ED8AA4AE3418ACB,
+               0x5B9CCA4F7763E373, 0x682E6FF3D6B2B8A3,
+               0x748F82EE5DEFB2FC, 0x78A5636F43172F60,
+               0x84C87814A1F0AB72, 0x8CC702081A6439EC,
+               0x90BEFFFA23631E28, 0xA4506CEBDE82BDE9,
+               0xBEF9A3F7B2C67915, 0xC67178F2E372532B,
+               0xCA273ECEEA26619C, 0xD186B8C721C0C207,
+               0xEADA7DD6CDE0EB1E, 0xF57D4F7FEE6ED178,
+               0x06F067AA72176FBA, 0x0A637DC5A2C898A6,
+               0x113F9804BEF90DAE, 0x1B710B35131C471B,
+               0x28DB77F523047D84, 0x32CAAB7B40C72493,
+               0x3C9EBE0A15C9BEBC, 0x431D67C49C100D4C,
+               0x4CC5D4BECB3E42B6, 0x597F299CFC657E2A,
+               0x5FCB6FAB3AD6FAEC, 0x6C44198C4A475817
+       };
+
+       void ProcessBlock()
+       {
+               /*
+                * Read state words.
+                */
+               ulong A = state[0];
+               ulong B = state[1];
+               ulong C = state[2];
+               ulong D = state[3];
+               ulong E = state[4];
+               ulong F = state[5];
+               ulong G = state[6];
+               ulong H = state[7];
+
+               ulong T1, T2;
+               ulong[] W = this.W;
+               byte[] block = this.block;
+
+               for (int i = 0, j = 0; i < 16; i ++, j += 8) {
+                       W[i] = Dec64be(block, j);
+               }
+               for (int i = 16; i < 80; i ++) {
+                       ulong w2 = W[i - 2];
+                       ulong w15 = W[i - 15];
+                       W[i] = (((w2 << 45) | (w2 >> 19))
+                               ^ ((w2 << 3) | (w2 >> 61))
+                               ^ (w2 >> 6))
+                               + W[i - 7]
+                               + (((w15 << 63) | (w15 >> 1))
+                               ^ ((w15 << 56) | (w15 >> 8))
+                               ^ (w15 >> 7))
+                               + W[i - 16];
+               }
+               for (int i = 0; i < 80; i += 8) {
+                       T1 = H + (((E << 50) | (E >> 14))
+                               ^ ((E << 46) | (E >> 18))
+                               ^ ((E << 23) | (E >> 41)))
+                               + (G ^ (E & (F ^ G)))
+                               + K[i + 0] + W[i + 0];
+                       T2 = (((A << 36) | (A >> 28))
+                               ^ ((A << 30) | (A >> 34))
+                               ^ ((A << 25) | (A >> 39)))
+                               + ((A & B) ^ (C & (A ^ B)));
+                       D += T1;
+                       H = T1 + T2;
+                       T1 = G + (((D << 50) | (D >> 14))
+                               ^ ((D << 46) | (D >> 18))
+                               ^ ((D << 23) | (D >> 41)))
+                               + (F ^ (D & (E ^ F)))
+                               + K[i + 1] + W[i + 1];
+                       T2 = (((H << 36) | (H >> 28))
+                               ^ ((H << 30) | (H >> 34))
+                               ^ ((H << 25) | (H >> 39)))
+                               + ((H & A) ^ (B & (H ^ A)));
+                       C += T1;
+                       G = T1 + T2;
+                       T1 = F + (((C << 50) | (C >> 14))
+                               ^ ((C << 46) | (C >> 18))
+                               ^ ((C << 23) | (C >> 41)))
+                               + (E ^ (C & (D ^ E)))
+                               + K[i + 2] + W[i + 2];
+                       T2 = (((G << 36) | (G >> 28))
+                               ^ ((G << 30) | (G >> 34))
+                               ^ ((G << 25) | (G >> 39)))
+                               + ((G & H) ^ (A & (G ^ H)));
+                       B += T1;
+                       F = T1 + T2;
+                       T1 = E + (((B << 50) | (B >> 14))
+                               ^ ((B << 46) | (B >> 18))
+                               ^ ((B << 23) | (B >> 41)))
+                               + (D ^ (B & (C ^ D)))
+                               + K[i + 3] + W[i + 3];
+                       T2 = (((F << 36) | (F >> 28))
+                               ^ ((F << 30) | (F >> 34))
+                               ^ ((F << 25) | (F >> 39)))
+                               + ((F & G) ^ (H & (F ^ G)));
+                       A += T1;
+                       E = T1 + T2;
+                       T1 = D + (((A << 50) | (A >> 14))
+                               ^ ((A << 46) | (A >> 18))
+                               ^ ((A << 23) | (A >> 41)))
+                               + (C ^ (A & (B ^ C)))
+                               + K[i + 4] + W[i + 4];
+                       T2 = (((E << 36) | (E >> 28))
+                               ^ ((E << 30) | (E >> 34))
+                               ^ ((E << 25) | (E >> 39)))
+                               + ((E & F) ^ (G & (E ^ F)));
+                       H += T1;
+                       D = T1 + T2;
+                       T1 = C + (((H << 50) | (H >> 14))
+                               ^ ((H << 46) | (H >> 18))
+                               ^ ((H << 23) | (H >> 41)))
+                               + (B ^ (H & (A ^ B)))
+                               + K[i + 5] + W[i + 5];
+                       T2 = (((D << 36) | (D >> 28))
+                               ^ ((D << 30) | (D >> 34))
+                               ^ ((D << 25) | (D >> 39)))
+                               + ((D & E) ^ (F & (D ^ E)));
+                       G += T1;
+                       C = T1 + T2;
+                       T1 = B + (((G << 50) | (G >> 14))
+                               ^ ((G << 46) | (G >> 18))
+                               ^ ((G << 23) | (G >> 41)))
+                               + (A ^ (G & (H ^ A)))
+                               + K[i + 6] + W[i + 6];
+                       T2 = (((C << 36) | (C >> 28))
+                               ^ ((C << 30) | (C >> 34))
+                               ^ ((C << 25) | (C >> 39)))
+                               + ((C & D) ^ (E & (C ^ D)));
+                       F += T1;
+                       B = T1 + T2;
+                       T1 = A + (((F << 50) | (F >> 14))
+                               ^ ((F << 46) | (F >> 18))
+                               ^ ((F << 23) | (F >> 41)))
+                               + (H ^ (F & (G ^ H)))
+                               + K[i + 7] + W[i + 7];
+                       T2 = (((B << 36) | (B >> 28))
+                               ^ ((B << 30) | (B >> 34))
+                               ^ ((B << 25) | (B >> 39)))
+                               + ((B & C) ^ (D & (B ^ C)));
+                       E += T1;
+                       A = T1 + T2;
+               }
+
+               /*
+                * Update state words and reset block pointer.
+                */
+               state[0] += A;
+               state[1] += B;
+               state[2] += C;
+               state[3] += D;
+               state[4] += E;
+               state[5] += F;
+               state[6] += G;
+               state[7] += H;
+               ptr = 0;
+       }
+
+       static ulong Dec64be(byte[] buf, int off)
+       {
+               return ((ulong)buf[off] << 56)
+                       | ((ulong)buf[off + 1] << 48)
+                       | ((ulong)buf[off + 2] << 40)
+                       | ((ulong)buf[off + 3] << 32)
+                       | ((ulong)buf[off + 4] << 24)
+                       | ((ulong)buf[off + 5] << 16)
+                       | ((ulong)buf[off + 6] << 8)
+                       | (ulong)buf[off + 7];
+       }
+
+       static void Enc64be(ulong x, byte[] buf, int off)
+       {
+               buf[off] = (byte)(x >> 56);
+               buf[off + 1] = (byte)(x >> 48);
+               buf[off + 2] = (byte)(x >> 40);
+               buf[off + 3] = (byte)(x >> 32);
+               buf[off + 4] = (byte)(x >> 24);
+               buf[off + 5] = (byte)(x >> 16);
+               buf[off + 6] = (byte)(x >> 8);
+               buf[off + 7] = (byte)x;
+       }
+}
+
+}
diff --git a/Crypto/SHA2Small.cs b/Crypto/SHA2Small.cs
new file mode 100644 (file)
index 0000000..fed41f4
--- /dev/null
@@ -0,0 +1,372 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * Implementation of SHA-224 and SHA-256, as described in FIPS 180-4.
+ */
+
+public abstract class SHA2Small : DigestCore {
+
+       const int BLOCK_LEN = 64;
+
+       uint[] state;
+       byte[] block, saveBlock;
+       int ptr;
+       ulong byteCount;
+       uint[] W;
+
+       /*
+        * Create a new instance, ready to process data bytes. The
+        * output length (in bytes) and initial value must be specified.
+        */
+       internal SHA2Small()
+       {
+               state = new uint[8];
+               block = new byte[BLOCK_LEN];
+               saveBlock = new byte[BLOCK_LEN];
+               W = new uint[64];
+               Reset();
+       }
+
+       internal abstract uint[] IV { get; }
+
+       internal abstract SHA2Small DupInner();
+
+       /* see IDigest */
+       public override int BlockSize {
+               get {
+                       return BLOCK_LEN;
+               }
+       }
+
+       /* see IDigest */
+       public override void Reset()
+       {
+               Array.Copy(IV, 0, state, 0, state.Length);
+               byteCount = 0;
+               ptr = 0;
+       }
+
+       /* see IDigest */
+       public override void Update(byte b)
+       {
+               block[ptr ++] = b;
+               byteCount ++;
+               if (ptr == BLOCK_LEN) {
+                       ProcessBlock();
+               }
+       }
+
+       /* see IDigest */
+       public override void Update(byte[] buf, int off, int len)
+       {
+               if (len < 0) {
+                       throw new ArgumentException("negative chunk length");
+               }
+               byteCount += (ulong)len;
+               while (len > 0) {
+                       int clen = Math.Min(len, BLOCK_LEN - ptr);
+                       Array.Copy(buf, off, block, ptr, clen);
+                       off += clen;
+                       len -= clen;
+                       ptr += clen;
+                       if (ptr == BLOCK_LEN) {
+                               ProcessBlock();
+                       }
+               }
+       }
+
+       /* see IDigest */
+       public override void DoPartial(byte[] outBuf, int off)
+       {
+               /*
+                * Save current state.
+                */
+               uint A = state[0];
+               uint B = state[1];
+               uint C = state[2];
+               uint D = state[3];
+               uint E = state[4];
+               uint F = state[5];
+               uint G = state[6];
+               uint H = state[7];
+               int savePtr = ptr;
+               Array.Copy(block, 0, saveBlock, 0, savePtr);
+
+               /*
+                * Add padding. This may involve processing an extra block.
+                */
+               block[ptr ++] = 0x80;
+               if (ptr > BLOCK_LEN - 8) {
+                       for (int j = ptr; j < BLOCK_LEN; j ++) {
+                               block[j] = 0;
+                       }
+                       ProcessBlock();
+               }
+               for (int j = ptr; j < (BLOCK_LEN - 8); j ++) {
+                       block[j] = 0;
+               }
+               ulong x = byteCount << 3;
+               Enc32be((uint)(x >> 32), block, BLOCK_LEN - 8);
+               Enc32be((uint)x, block, BLOCK_LEN - 4);
+
+               /*
+                * Process final block and encode result.
+                */
+               ProcessBlock();
+               int n = DigestSize >> 2;
+               for (int i = 0; i < n; i ++) {
+                       Enc32be(state[i], outBuf, off + (i << 2));
+               }
+
+               /*
+                * Restore current state.
+                */
+               Array.Copy(saveBlock, 0, block, 0, savePtr);
+               state[0] = A;
+               state[1] = B;
+               state[2] = C;
+               state[3] = D;
+               state[4] = E;
+               state[5] = F;
+               state[6] = G;
+               state[7] = H;
+               ptr = savePtr;
+       }
+
+       /* see IDigest */
+       public override IDigest Dup()
+       {
+               SHA2Small h = DupInner();
+               Array.Copy(state, 0, h.state, 0, state.Length);
+               h.ptr = ptr;
+               h.byteCount = byteCount;
+               Array.Copy(block, 0, h.block, 0, ptr);
+               return h;
+       }
+
+       /* see IDigest */
+       public override void CurrentState(byte[] outBuf, int off)
+       {
+               int n = DigestSize >> 2;
+               for (int i = 0; i < n; i ++) {
+                       Enc32be(state[i], outBuf, off + (i << 2));
+               }
+       }
+
+       static uint[] K = {
+               0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5,
+               0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5,
+               0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3,
+               0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174,
+               0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC,
+               0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA,
+               0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7,
+               0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967,
+               0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13,
+               0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85,
+               0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3,
+               0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070,
+               0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5,
+               0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3,
+               0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208,
+               0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2
+       };
+
+       void ProcessBlock()
+       {
+               /*
+                * Read state words.
+                */
+               uint A = state[0];
+               uint B = state[1];
+               uint C = state[2];
+               uint D = state[3];
+               uint E = state[4];
+               uint F = state[5];
+               uint G = state[6];
+               uint H = state[7];
+
+               uint T1, T2;
+               uint[] W = this.W;
+               byte[] block = this.block;
+
+               for (int i = 0, j = 0; i < 16; i ++, j += 4) {
+                       W[i] = Dec32be(block, j);
+               }
+               for (int i = 16; i < 64; i ++) {
+                       uint w2 = W[i - 2];
+                       uint w15 = W[i - 15];
+                       W[i] = (((w2 << 15) | (w2 >> 17))
+                               ^ ((w2 << 13) | (w2 >> 19))
+                               ^ (w2 >> 10))
+                               + W[i - 7]
+                               + (((w15 << 25) | (w15 >> 7))
+                               ^ ((w15 << 14) | (w15 >> 18))
+                               ^ (w15 >> 3))
+                               + W[i - 16];
+               }
+               for (int i = 0; i < 64; i += 8) {
+                       T1 = H + (((E << 26) | (E >> 6))
+                               ^ ((E << 21) | (E >> 11))
+                               ^ ((E << 7) | (E >> 25)))
+                               + (G ^ (E & (F ^ G)))
+                               + K[i + 0] + W[i + 0];
+                       T2 = (((A << 30) | (A >> 2))
+                               ^ ((A << 19) | (A >> 13))
+                               ^ ((A << 10) | (A >> 22)))
+                               + ((A & B) ^ (C & (A ^ B)));
+                       D += T1;
+                       H = T1 + T2;
+                       T1 = G + (((D << 26) | (D >> 6))
+                               ^ ((D << 21) | (D >> 11))
+                               ^ ((D << 7) | (D >> 25)))
+                               + (F ^ (D & (E ^ F)))
+                               + K[i + 1] + W[i + 1];
+                       T2 = (((H << 30) | (H >> 2))
+                               ^ ((H << 19) | (H >> 13))
+                               ^ ((H << 10) | (H >> 22)))
+                               + ((H & A) ^ (B & (H ^ A)));
+                       C += T1;
+                       G = T1 + T2;
+                       T1 = F + (((C << 26) | (C >> 6))
+                               ^ ((C << 21) | (C >> 11))
+                               ^ ((C << 7) | (C >> 25)))
+                               + (E ^ (C & (D ^ E)))
+                               + K[i + 2] + W[i + 2];
+                       T2 = (((G << 30) | (G >> 2))
+                               ^ ((G << 19) | (G >> 13))
+                               ^ ((G << 10) | (G >> 22)))
+                               + ((G & H) ^ (A & (G ^ H)));
+                       B += T1;
+                       F = T1 + T2;
+                       T1 = E + (((B << 26) | (B >> 6))
+                               ^ ((B << 21) | (B >> 11))
+                               ^ ((B << 7) | (B >> 25)))
+                               + (D ^ (B & (C ^ D)))
+                               + K[i + 3] + W[i + 3];
+                       T2 = (((F << 30) | (F >> 2))
+                               ^ ((F << 19) | (F >> 13))
+                               ^ ((F << 10) | (F >> 22)))
+                               + ((F & G) ^ (H & (F ^ G)));
+                       A += T1;
+                       E = T1 + T2;
+                       T1 = D + (((A << 26) | (A >> 6))
+                               ^ ((A << 21) | (A >> 11))
+                               ^ ((A << 7) | (A >> 25)))
+                               + (C ^ (A & (B ^ C)))
+                               + K[i + 4] + W[i + 4];
+                       T2 = (((E << 30) | (E >> 2))
+                               ^ ((E << 19) | (E >> 13))
+                               ^ ((E << 10) | (E >> 22)))
+                               + ((E & F) ^ (G & (E ^ F)));
+                       H += T1;
+                       D = T1 + T2;
+                       T1 = C + (((H << 26) | (H >> 6))
+                               ^ ((H << 21) | (H >> 11))
+                               ^ ((H << 7) | (H >> 25)))
+                               + (B ^ (H & (A ^ B)))
+                               + K[i + 5] + W[i + 5];
+                       T2 = (((D << 30) | (D >> 2))
+                               ^ ((D << 19) | (D >> 13))
+                               ^ ((D << 10) | (D >> 22)))
+                               + ((D & E) ^ (F & (D ^ E)));
+                       G += T1;
+                       C = T1 + T2;
+                       T1 = B + (((G << 26) | (G >> 6))
+                               ^ ((G << 21) | (G >> 11))
+                               ^ ((G << 7) | (G >> 25)))
+                               + (A ^ (G & (H ^ A)))
+                               + K[i + 6] + W[i + 6];
+                       T2 = (((C << 30) | (C >> 2))
+                               ^ ((C << 19) | (C >> 13))
+                               ^ ((C << 10) | (C >> 22)))
+                               + ((C & D) ^ (E & (C ^ D)));
+                       F += T1;
+                       B = T1 + T2;
+                       T1 = A + (((F << 26) | (F >> 6))
+                               ^ ((F << 21) | (F >> 11))
+                               ^ ((F << 7) | (F >> 25)))
+                               + (H ^ (F & (G ^ H)))
+                               + K[i + 7] + W[i + 7];
+                       T2 = (((B << 30) | (B >> 2))
+                               ^ ((B << 19) | (B >> 13))
+                               ^ ((B << 10) | (B >> 22)))
+                               + ((B & C) ^ (D & (B ^ C)));
+                       E += T1;
+                       A = T1 + T2;
+               }
+
+               /* obsolete
+               for (int i = 0; i < 64; i ++) {
+                       uint T1 = H + (((E << 26) | (E >> 6))
+                               ^ ((E << 21) | (E >> 11))
+                               ^ ((E << 7) | (E >> 25)))
+                               + (G ^ (E & (F ^ G)))
+                               + K[i] + W[i];
+                       uint T2 = (((A << 30) | (A >> 2))
+                               ^ ((A << 19) | (A >> 13))
+                               ^ ((A << 10) | (A >> 22)))
+                               + ((A & B) ^ (C & (A ^ B)));
+                       H = G; G = F; F = E; E = D + T1;
+                       D = C; C = B; B = A; A = T1 + T2;
+               }
+               */
+
+               /*
+                * Update state words and reset block pointer.
+                */
+               state[0] += A;
+               state[1] += B;
+               state[2] += C;
+               state[3] += D;
+               state[4] += E;
+               state[5] += F;
+               state[6] += G;
+               state[7] += H;
+               ptr = 0;
+       }
+
+       static uint Dec32be(byte[] buf, int off)
+       {
+               return ((uint)buf[off] << 24)
+                       | ((uint)buf[off + 1] << 16)
+                       | ((uint)buf[off + 2] << 8)
+                       | (uint)buf[off + 3];
+       }
+
+       static void Enc32be(uint x, byte[] buf, int off)
+       {
+               buf[off] = (byte)(x >> 24);
+               buf[off + 1] = (byte)(x >> 16);
+               buf[off + 2] = (byte)(x >> 8);
+               buf[off + 3] = (byte)x;
+       }
+}
+
+}
diff --git a/Crypto/SHA384.cs b/Crypto/SHA384.cs
new file mode 100644 (file)
index 0000000..27eac33
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * SHA-384 implementation. SHA-384 is described in FIPS 180-4.
+ */
+
+public sealed class SHA384 : SHA2Big {
+
+       /*
+        * Create a new instance, ready to process data bytes.
+        */
+       public SHA384()
+       {
+       }
+
+       /* see IDigest */
+       public override string Name {
+               get {
+                       return "SHA-384";
+               }
+       }
+
+       /* see IDigest */
+       public override int DigestSize {
+               get {
+                       return 48;
+               }
+       }
+
+       internal override ulong[] IV {
+               get {
+                       return IV384;
+               }
+       }
+
+       internal override SHA2Big DupInner()
+       {
+               return new SHA384();
+       }
+
+       static ulong[] IV384 = {
+               0xCBBB9D5DC1059ED8, 0x629A292A367CD507,
+               0x9159015A3070DD17, 0x152FECD8F70E5939,
+               0x67332667FFC00B31, 0x8EB44A8768581511,
+               0xDB0C2E0D64F98FA7, 0x47B5481DBEFA4FA4
+       };
+}
+
+}
diff --git a/Crypto/SHA512.cs b/Crypto/SHA512.cs
new file mode 100644 (file)
index 0000000..b5fb22b
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace Crypto {
+
+/*
+ * SHA-512 implementation. SHA-512 is described in FIPS 180-4.
+ */
+
+public sealed class SHA512 : SHA2Big {
+
+       /*
+        * Create a new instance, ready to process data bytes.
+        */
+       public SHA512()
+       {
+       }
+
+       /* see IDigest */
+       public override string Name {
+               get {
+                       return "SHA-512";
+               }
+       }
+
+       /* see IDigest */
+       public override int DigestSize {
+               get {
+                       return 64;
+               }
+       }
+
+       internal override ulong[] IV {
+               get {
+                       return IV512;
+               }
+       }
+
+       internal override SHA2Big DupInner()
+       {
+               return new SHA512();
+       }
+
+       static ulong[] IV512 = {
+               0x6A09E667F3BCC908, 0xBB67AE8584CAA73B,
+               0x3C6EF372FE94F82B, 0xA54FF53A5F1D36F1,
+               0x510E527FADE682D1, 0x9B05688C2B3E6C1F,
+               0x1F83D9ABFB41BD6B, 0x5BE0CD19137E2179
+       };
+}
+
+}
diff --git a/SSLTLS/IO.cs b/SSLTLS/IO.cs
new file mode 100644 (file)
index 0000000..82b8331
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.IO;
+
+namespace SSLTLS {
+
+internal class IO {
+
+       internal static void Enc16be(int x, byte[] buf, int off)
+       {
+               buf[off + 0] = (byte)(x >> 8);
+               buf[off + 1] = (byte)x;
+       }
+
+       internal static void Enc24be(int x, byte[] buf, int off)
+       {
+               buf[off + 0] = (byte)(x >> 16);
+               buf[off + 1] = (byte)(x >> 8);
+               buf[off + 2] = (byte)x;
+       }
+
+       internal static void Enc32be(uint x, byte[] buf, int off)
+       {
+               buf[off + 0] = (byte)(x >> 24);
+               buf[off + 1] = (byte)(x >> 16);
+               buf[off + 2] = (byte)(x >> 8);
+               buf[off + 3] = (byte)x;
+       }
+
+       internal static void Enc64be(ulong x, byte[] buf, int off)
+       {
+               for (int i = 0; i < 8; i ++) {
+                       buf[off + 7 - i] = (byte)x;
+                       x >>= 8;
+               }
+       }
+
+       internal static int Dec16be(byte[] buf, int off)
+       {
+               return (buf[off + 0] << 8)
+                       | buf[off + 1];
+       }
+
+       internal static int Dec24be(byte[] buf, int off)
+       {
+               return (buf[off + 0] << 16)
+                       | (buf[off + 1] << 8)
+                       | buf[off + 2];
+       }
+
+       internal static uint Dec32be(byte[] buf, int off)
+       {
+               return ((uint)buf[off + 0] << 24)
+                       | ((uint)buf[off + 1] << 16)
+                       | ((uint)buf[off + 2] << 8)
+                       | (uint)buf[off + 3];
+       }
+
+       internal static ulong Dec64be(byte[] buf, int off)
+       {
+               ulong x = 0;
+               for (int i = 0; i < 8; i ++) {
+                       x = (x << 8) | buf[off + i];
+               }
+               return x;
+       }
+
+       internal static void Write16(Stream s, int x)
+       {
+               s.WriteByte((byte)(x >> 8));
+               s.WriteByte((byte)x);
+       }
+
+       internal static void Write24(Stream s, int x)
+       {
+               s.WriteByte((byte)(x >> 16));
+               s.WriteByte((byte)(x >> 8));
+               s.WriteByte((byte)x);
+       }
+
+       /*
+        * Write a 5-byte record header at the specified offset.
+        */
+       internal static void WriteHeader(int recordType, int version,
+               int length, byte[] buf, int off)
+       {
+               buf[off] = (byte)recordType;
+               IO.Enc16be(version, buf, off + 1);
+               IO.Enc16be(length, buf, off + 3);
+       }
+
+       /*
+        * Read all requested bytes. Fail on unexpected EOF, unless
+        * 'eof' is true, in which case an EOF is acceptable at the
+        * very beginning (i.e. no byte read at all).
+        *
+        * Returned value is true, unless there was an EOF at the
+        * start and 'eof' is true, in which case returned value is
+        * false.
+        */
+       internal static bool ReadAll(Stream s, byte[] buf, bool eof)
+       {
+               return ReadAll(s, buf, 0, buf.Length, eof);
+       }
+
+       /*
+        * Read all requested bytes. Fail on unexpected EOF, unless
+        * 'eof' is true, in which case an EOF is acceptable at the
+        * very beginning (i.e. no byte read at all).
+        *
+        * Returned value is true, unless there was an EOF at the
+        * start and 'eof' is true, in which case returned value is
+        * false.
+        */
+       internal static bool ReadAll(Stream s,
+               byte[] buf, int off, int len, bool eof)
+       {
+               int tlen = 0;
+               while (tlen < len) {
+                       int rlen = s.Read(buf, off + tlen, len - tlen);
+                       if (rlen <= 0) {
+                               if (eof && tlen == 0) {
+                                       return false;
+                               }
+                               throw new SSLException("Unexpected EOF");
+                       }
+                       tlen += rlen;
+               }
+               return true;
+       }
+
+       /*
+        * Compare two arrays of bytes for equality.
+        */
+       internal static bool Eq(byte[] a, byte[] b)
+       {
+               return EqCT(a, b) != 0;
+       }
+
+       /*
+        * Compare two arrays of bytes for equality. This is constant-time
+        * if both arrays have the same length. Returned value is 0xFFFFFFFF
+        * on equality, 0 otherwise.
+        */
+       internal static uint EqCT(byte[] a, byte[] b)
+       {
+               int n = a.Length;
+               if (n != b.Length) {
+                       return 0;
+               }
+               int z = 0;
+               for (int i = 0; i < n; i ++) {
+                       z |= a[i] ^ b[i];
+               }
+               return ~(uint)((z | -z) >> 31);
+       }
+
+       /*
+        * Duplicate an array of bytes. null is duplicated into null.
+        */
+       internal static byte[] CopyBlob(byte[] x)
+       {
+               if (x == null) {
+                       return null;
+               }
+               byte[] y = new byte[x.Length];
+               Array.Copy(x, 0, y, 0, x.Length);
+               return y;
+       }
+}
+
+}
diff --git a/SSLTLS/IServerChoices.cs b/SSLTLS/IServerChoices.cs
new file mode 100644 (file)
index 0000000..cf130e7
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.IO;
+
+namespace SSLTLS {
+
+/*
+ * An IServerChoices instance represents the server policy choices for
+ * a given connection. It returns the certificate chain and cipher suite
+ * to send to the client, and it computes private key operations.
+ */
+
+public interface IServerChoices {
+
+       /*
+        * Get the selected cipher suite.
+        */
+       int GetCipherSuite();
+
+       /*
+        * Get the certificate chain to send to the client.
+        */
+       byte[][] GetCertificateChain();
+
+       /*
+        * Compute the key exchange, based on the value sent by the
+        * client. Returned value is the premaster secret. This method
+        * is invoked only if the selected cipher suite is of type
+        * RSA or static ECDH.
+        */
+       byte[] DoKeyExchange(byte[] cke);
+
+       /*
+        * Compute the signature on the provided ServerKeyExchange
+        * message. The 'hashAlgo' and 'sigAlgo' values are set to
+        * the symbolic values corresponding to the used signature
+        * algorithm. This method is invoked only if the selected cipher
+        * suite is of type ECDHE.
+        */
+       byte[] DoSign(byte[] ske, out int hashAlgo, out int sigAlgo);
+}
+
+}
diff --git a/SSLTLS/IServerPolicy.cs b/SSLTLS/IServerPolicy.cs
new file mode 100644 (file)
index 0000000..38f914e
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.IO;
+
+namespace SSLTLS {
+
+/*
+ * An IServerPolicy instance is a callback object that selects the
+ * cipher suite and certificate chain to send to the client, and
+ * implements the private key operation (signature or key exchange).
+ */
+
+public interface IServerPolicy {
+
+       /*
+        * Get the server policy choices for the provided connection.
+        * In the 'server' object, the following are already set:
+        *
+        *   Version               Selected protocol version.
+        *
+        *   CommonCipherSuites    Common cipher suites.
+        *
+        *   ClientCurves          Elliptic curve supported by the client.
+        *
+        *   CommonCurves          Common supported curves.
+        *
+        *   ClientHashAndSign     Common hash and signature algorithms.
+        *
+        * Returned value is a callback object that embodies the choices
+        * and will perform private key operations.
+        */
+       IServerChoices Apply(SSLServer server);
+}
+
+}
diff --git a/SSLTLS/ISessionCache.cs b/SSLTLS/ISessionCache.cs
new file mode 100644 (file)
index 0000000..077e6f6
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.IO;
+
+namespace SSLTLS {
+
+/*
+ * A session cache instance is able to cache and remember session
+ * parameters; this is typically used on a SSL server.
+ */
+
+public interface ISessionCache {
+
+       /*
+        * Retrieve session parameters by session ID. If the client sent
+        * an intended server name (SNI extension), then that name is
+        * also provided as parameter (printable ASCII, normalised to
+        * lowercase, no space); otherwise, that parameter is null.
+        * Session cache implementations are free to use the server name
+        * or not; if the client specified a target name, and the cache
+        * returns parameters with a different, non-null name, then the
+        * session resumption will be rejected by the engine.
+        *
+        * If no parameters are found for that ID (and optional server
+        * name), then this method shall return null.
+        */
+       SSLSessionParameters Retrieve(byte[] id, string serverName);
+
+       /*
+        * Record new session parameters. These should be internally
+        * indexed by their ID.
+        */
+       void Store(SSLSessionParameters sp);
+}
+
+}
diff --git a/SSLTLS/InputRecord.cs b/SSLTLS/InputRecord.cs
new file mode 100644 (file)
index 0000000..20d3df1
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.IO;
+
+namespace SSLTLS {
+
+internal class InputRecord {
+
+       Stream sub;
+       byte[] buffer;
+       int recordPtr, recordEnd;
+       int recordType;
+       int recordVersion;
+       int expectedVersion;
+       RecordDecrypt rdec;
+
+       /*
+        * Get the length (in bytes) of the data that remains to be
+        * read from the received buffer.
+        */
+       internal int BufferedLength {
+               get {
+                       return recordEnd - recordPtr;
+               }
+       }
+
+       /*
+        * Get the current record type (-1 if no record was read yet).
+        */
+       internal int RecordType {
+               get {
+                       return recordType;
+               }
+       }
+
+       /*
+        * Get the current record version (-1 if no record was read yet).
+        */
+       internal int RecordVersion {
+               get {
+                       return recordVersion;
+               }
+       }
+
+       internal InputRecord(Stream sub)
+       {
+               this.sub = sub;
+               buffer = new byte[16384 + 500];
+               recordPtr = 0;
+               recordEnd = 0;
+               recordType = -1;
+               recordVersion = -1;
+               expectedVersion = -1;
+               rdec = new RecordDecryptPlain();
+       }
+
+       /*
+        * Set the expected version. If this value is nonnegative, then
+        * all subsequent records are expected to match this version; a
+        * version mismatch will trigger an exception.
+        *
+        * If not initially set explicitly, then this value is automatically
+        * set to the version of the first incoming record.
+        */
+       internal void SetExpectedVersion(int expectedVersion)
+       {
+               this.expectedVersion = expectedVersion;
+       }
+
+       /*
+        * Set the new decryption engine. This is possible only if the
+        * end of the current record was reached.
+        */
+       internal void SetDecryption(RecordDecrypt rdec)
+       {
+               if (recordPtr != recordEnd) {
+                       throw new SSLException(
+                               "Cannot switch encryption: buffered data");
+               }
+               this.rdec = rdec;
+       }
+
+       /*
+        * Get next record. Returned value is false if EOF was reached
+        * before obtaining the first record header byte.
+        */
+       internal bool NextRecord()
+       {
+               if (!IO.ReadAll(sub, buffer, 0, 5, true)) {
+                       return false;
+               }
+               recordType = buffer[0];
+               recordVersion = IO.Dec16be(buffer, 1);
+               int len = IO.Dec16be(buffer, 3);
+               if (expectedVersion >= 0 && expectedVersion != recordVersion) {
+                       throw new SSLException(string.Format(
+                               "Wrong record version: 0x{0:X4}"
+                               + " (expected: 0x{1:X4})",
+                               recordVersion, expectedVersion));
+               } else {
+                       if ((recordVersion >> 8) != 0x03) {
+                               throw new SSLException(string.Format(
+                                       "Unsupported record version: 0x{0:X4}",
+                                       recordVersion));
+                       }
+                       if (expectedVersion < 0) {
+                               expectedVersion = recordVersion;
+                       }
+               }
+               if (!rdec.CheckLength(len)) {
+                       throw new SSLException("Wrong record length: " + len);
+               }
+               IO.ReadAll(sub, buffer, 0, len, false);
+               int off = 0;
+               if (!rdec.Decrypt(recordType, recordVersion,
+                       buffer, ref off, ref len))
+               {
+                       throw new SSLException("Decryption failure");
+               }
+               recordPtr = off;
+               recordEnd = off + len;
+               return true;
+       }
+
+       /*
+        * Read the next byte from the current record. -1 is returned if
+        * the current record is finished.
+        */
+       internal int Read()
+       {
+               if (recordPtr == recordEnd) {
+                       return -1;
+               } else {
+                       return buffer[recordPtr ++];
+               }
+       }
+
+       /*
+        * Read some bytes from the current record. The number of
+        * obtained bytes is returned; a short count (including 0)
+        * is possible only if the end of the current record was
+        * reached.
+        */
+       internal int Read(byte[] buf)
+       {
+               return Read(buf, 0, buf.Length);
+       }
+
+       /*
+        * Read some bytes from the current record. The number of
+        * obtained bytes is returned; a short count (including 0)
+        * is possible only if the end of the current record was
+        * reached.
+        */
+       internal int Read(byte[] buf, int off, int len)
+       {
+               int clen = Math.Min(len, recordEnd - recordPtr);
+               Array.Copy(buffer, recordPtr, buf, off, clen);
+               recordPtr += clen;
+               return clen;
+       }
+}
+
+}
diff --git a/SSLTLS/KeyUsage.cs b/SSLTLS/KeyUsage.cs
new file mode 100644 (file)
index 0000000..4cbd144
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace SSLTLS {
+
+/*
+ * Symbolic constants for acceptable usages of a public key:
+ *
+ *   EncryptOnly      public key may be used for asymmetric encryption or
+ *                    key exchange (TLS_RSA_* and TLS_ECDH_*), but not for
+ *                    signatures
+ *
+ *   SignOnly         public key may be used for signatures only
+ *                    (TLS_ECDHE_*), but not for asymmetric encryption or
+ *                    key exchange
+ *
+ *   EncryptAndSign   public key may be used for asymmetric encryption,
+ *                    key exchange and signatures
+ */
+
+public enum KeyUsage {
+       EncryptOnly,
+       SignOnly,
+       EncryptAndSign
+}
+
+}
diff --git a/SSLTLS/OutputRecord.cs b/SSLTLS/OutputRecord.cs
new file mode 100644 (file)
index 0000000..ebb81c9
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.IO;
+
+namespace SSLTLS {
+
+internal class OutputRecord {
+
+       /*
+        * Splitting modes are for debugging and tests. Note that the
+        * automatic 1/n-1 split for CBC cipher suites in TLS 1.0 is
+        * handled independently in RecordEncryptCBC.
+        */
+
+       /* No split. */
+       internal const int MODE_NORMAL = 0;
+
+       /* Each record is split into two, of approximately the same size. */
+       internal const int MODE_SPLIT_HALF = 1;
+
+       /* Each record is preceded with an extra record of size 0. */
+       internal const int MODE_SPLIT_ZERO_BEFORE = 2;
+
+       /* Each record is split into two records (like SPLIT_HALF), and
+          an extra zero-length record is added between the two halves. */
+       internal const int MODE_SPLIT_ZERO_HALF = 3;
+
+       /* The first byte of each record is separated into its own record. */
+       internal const int MODE_SPLIT_ONE_START = 4;
+
+       /* The last byte of each record is separated into its own record. */
+       internal const int MODE_SPLIT_ONE_END = 5;
+
+       /* The record is split into records of length 1 byte each. */
+       internal const int MODE_SPLIT_MULTI_ONE = 6;
+
+       /*
+        * Spliting modes are only applied on the specified record types
+        * (these are bit flags that can be combined).
+        */
+       internal const int MODE_MT_CCS = 1 << SSL.CHANGE_CIPHER_SPEC;
+       internal const int MODE_MT_ALERT = 1 << SSL.ALERT;
+       internal const int MODE_MT_HANDSHAKE = 1 << SSL.HANDSHAKE;
+       internal const int MODE_MT_APPLICATION_DATA = 1 << SSL.APPLICATION_DATA;
+
+       const int MODE_MASK = 0xFFFF;
+
+       Stream sub;
+       byte[] buffer;
+       int ptr, basePtr, maxPtr;
+       int version;
+       int recordType;
+       RecordEncrypt renc;
+
+       int splitMode;
+       byte[] extra;
+
+       long countHandshake;
+       long countAppData;
+       long thresholdZeroHandshake;
+       long thresholdZeroAppData;
+       byte[] extra2;
+
+       internal OutputRecord(Stream sub)
+       {
+               this.sub = sub;
+               buffer = new byte[16384 + 500];
+               version = 0;
+               recordType = -1;
+               splitMode = MODE_NORMAL;
+               extra = null;
+               countHandshake = 0;
+               countAppData = 0;
+               thresholdZeroHandshake = 0;
+               thresholdZeroAppData = 0;
+               extra2 = new byte[500];
+               renc = new RecordEncryptPlain();
+               PrepNew();
+       }
+
+       /*
+        * If set, then all I/O errors while writing on the underlying
+        * stream will be converted to a generic SSLException with message
+        * "Unexpected transport closure". This helps test code that
+        * expects the peer to abort asynchronously, so the error may
+        * be detected during both reading or writing.
+        */
+       internal bool NormalizeIOError {
+               get; set;
+       }
+
+       internal void SetVersion(int version)
+       {
+               if (version != this.version) {
+                       if (ptr != basePtr) {
+                               FlushInner();
+                       }
+                       this.version = version;
+                       PrepNew();
+               }
+       }
+
+       internal int RecordType {
+               get {
+                       return recordType;
+               }
+               set {
+                       if (value != recordType) {
+                               if (ptr != basePtr) {
+                                       FlushInner();
+                               }
+                               recordType = value;
+                       }
+               }
+       }
+
+       internal void SetEncryption(RecordEncrypt renc)
+       {
+               if (ptr != basePtr) {
+                       FlushInner();
+               }
+               this.renc = renc;
+               PrepNew();
+       }
+
+       internal void SetSplitMode(int splitMode)
+       {
+               this.splitMode = splitMode;
+               if ((splitMode & MODE_MASK) != MODE_NORMAL && extra == null) {
+                       extra = new byte[buffer.Length];
+               }
+       }
+
+       internal void SetThresholdZeroAppData(long t)
+       {
+               thresholdZeroAppData = t;
+       }
+
+       internal void SetThresholdZeroHandshake(long t)
+       {
+               thresholdZeroAppData = t;
+       }
+
+       void PrepNew()
+       {
+               int start = 0;
+               int end = buffer.Length;
+               renc.GetMaxPlaintext(ref start, ref end);
+               ptr = start;
+               basePtr = start;
+               maxPtr = end;
+       }
+
+       internal void Flush()
+       {
+               if (ptr == basePtr) {
+                       return;
+               }
+               FlushInner();
+               sub.Flush();
+       }
+
+       internal void SendZeroLength(int type)
+       {
+               Flush();
+               int rt = RecordType;
+               RecordType = type;
+               FlushInner();
+               RecordType = rt;
+               sub.Flush();
+       }
+
+       void FlushInner()
+       {
+               int off = basePtr;
+               int len = ptr - basePtr;
+               if (version == 0) {
+                       throw new Exception("Record version is not set");
+               }
+               int m = splitMode & MODE_MASK;
+               if (m == MODE_NORMAL || (splitMode & (1 << recordType)) == 0) {
+                       EncryptAndWrite(off, len);
+               } else {
+                       Array.Copy(buffer, off, extra, off, len);
+                       switch (m) {
+                       case MODE_SPLIT_HALF:
+                       case MODE_SPLIT_ZERO_HALF:
+                               int hlen = (len >> 1);
+                               if (hlen > 0) {
+                                       EncryptAndWrite(off, hlen);
+                               }
+                               if (m == MODE_SPLIT_ZERO_HALF) {
+                                       EncryptAndWrite(off, 0);
+                               }
+                               Array.Copy(extra, off + hlen,
+                                       buffer, off, len - hlen);
+                               hlen = len - hlen;
+                               if (hlen > 0) {
+                                       EncryptAndWrite(off, hlen);
+                               }
+                               break;
+                       case MODE_SPLIT_ZERO_BEFORE:
+                               EncryptAndWrite(off, 0);
+                               Array.Copy(extra, off, buffer, off, len);
+                               if (len > 0) {
+                                       EncryptAndWrite(off, len);
+                               }
+                               break;
+                       case MODE_SPLIT_ONE_START:
+                               if (len > 0) {
+                                       EncryptAndWrite(off, 1);
+                               }
+                               if (len > 1) {
+                                       Array.Copy(extra, off + 1,
+                                               buffer, off, len - 1);
+                                       EncryptAndWrite(off, len - 1);
+                               }
+                               break;
+                       case MODE_SPLIT_ONE_END:
+                               if (len > 1) {
+                                       EncryptAndWrite(off, len - 1);
+                               }
+                               if (len > 0) {
+                                       buffer[off] = extra[off + len - 1];
+                                       EncryptAndWrite(off, 1);
+                               }
+                               break;
+                       case MODE_SPLIT_MULTI_ONE:
+                               for (int i = 0; i < len; i ++) {
+                                       buffer[off] = extra[off + i];
+                                       EncryptAndWrite(off, 1);
+                               }
+                               break;
+                       default:
+                               throw new SSLException(string.Format(
+                                       "Bad record splitting value: {0}", m));
+                       }
+               }
+               PrepNew();
+       }
+
+       void EncryptAndWrite(int off, int len)
+       {
+               try {
+                       EncryptAndWriteInner(off, len);
+               } catch {
+                       if (NormalizeIOError) {
+                               throw new SSLException(
+                                       "Unexpected transport closure");
+                       } else {
+                               throw;
+                       }
+               }
+       }
+
+       void EncryptAndWriteInner(int off, int len)
+       {
+               if (recordType == SSL.HANDSHAKE) {
+                       countHandshake ++;
+                       if (countHandshake == thresholdZeroAppData) {
+                               int start = 0;
+                               int end = extra2.Length;
+                               renc.GetMaxPlaintext(ref start, ref end);
+                               int zoff = start;
+                               int zlen = 0;
+                               renc.Encrypt(SSL.APPLICATION_DATA, version,
+                                       extra2, ref zoff, ref zlen);
+                               sub.Write(extra2, zoff, zlen);
+                       }
+               } else if (recordType == SSL.APPLICATION_DATA) {
+                       countAppData ++;
+                       if (countAppData == thresholdZeroHandshake) {
+                               int start = 0;
+                               int end = extra2.Length;
+                               renc.GetMaxPlaintext(ref start, ref end);
+                               int zoff = start;
+                               int zlen = 0;
+                               renc.Encrypt(SSL.HANDSHAKE, version,
+                                       extra2, ref zoff, ref zlen);
+                               sub.Write(extra2, zoff, zlen);
+                       }
+               }
+
+               renc.Encrypt(recordType, version, buffer, ref off, ref len);
+               sub.Write(buffer, off, len);
+       }
+
+       internal void Write(byte x)
+       {
+               buffer[ptr ++] = x;
+               if (ptr == maxPtr) {
+                       FlushInner();
+               }
+       }
+
+       internal void Write(byte[] data)
+       {
+               Write(data, 0, data.Length);
+       }
+
+       internal void Write(byte[] data, int off, int len)
+       {
+               while (len > 0) {
+                       int clen = Math.Min(len, maxPtr - ptr);
+                       Array.Copy(data, off, buffer, ptr, clen);
+                       ptr += clen;
+                       off += clen;
+                       len -= clen;
+                       if (ptr == maxPtr) {
+                               FlushInner();
+                       }
+               }
+       }
+}
+
+}
diff --git a/SSLTLS/PRF.cs b/SSLTLS/PRF.cs
new file mode 100644 (file)
index 0000000..db8427e
--- /dev/null
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+/*
+ * Implementation of the TLS PRF function. This class implements both the
+ * PRF for TLS 1.0 and 1.1 (based on MD5 and SHA-1), and the PRF for
+ * TLS 1.2 (based on a provided hash function).
+ */
+
+public sealed class PRF {
+
+       public static byte[] LABEL_MASTER_SECRET =
+               Encoding.UTF8.GetBytes("master secret");
+       public static byte[] LABEL_KEY_EXPANSION =
+               Encoding.UTF8.GetBytes("key expansion");
+       public static byte[] LABEL_CLIENT_FINISHED =
+               Encoding.UTF8.GetBytes("client finished");
+       public static byte[] LABEL_SERVER_FINISHED =
+               Encoding.UTF8.GetBytes("server finished");
+
+       HMAC hm1, hm2;
+       byte[] bufa1, bufa2;
+       byte[] bufb1, bufb2;
+
+       /*
+        * Create a PRF instance, using both MD5 and SHA-1 (for TLS 1.0
+        * and TLS 1.1).
+        */
+       public PRF() : this(new MD5(), new SHA1())
+       {
+       }
+
+       /*
+        * Create a PRF instance, using the provided hash function (for
+        * TLS 1.2). The 'h' instance will be used internally.
+        */
+       public PRF(IDigest h) : this(h, null)
+       {
+       }
+
+       /*
+        * Get the "natural" output length; this is the output size,
+        * or sum of output sizes, of the underlying hash function(s).
+        */
+       public int NaturalOutputSize {
+               get {
+                       int len = hm1.MACSize;
+                       if (hm2 != null) {
+                               len += hm2.MACSize;
+                       }
+                       return len;
+               }
+       }
+
+       PRF(IDigest h1, IDigest h2)
+       {
+               hm1 = new HMAC(h1);
+               bufa1 = new byte[hm1.MACSize];
+               bufb1 = new byte[hm1.MACSize];
+               if (h2 == null) {
+                       hm2 = null;
+                       bufa2 = null;
+                       bufb2 = null;
+               } else {
+                       hm2 = new HMAC(h2);
+                       bufa2 = new byte[hm2.MACSize];
+                       bufb2 = new byte[hm2.MACSize];
+               }
+       }
+
+       /*
+        * Compute the PRF, result in outBuf[].
+        */
+       public void GetBytes(byte[] secret, byte[] label, byte[] seed,
+               byte[] outBuf)
+       {
+               GetBytes(secret, label, seed, outBuf, 0, outBuf.Length);
+       }
+
+       /*
+        * Compute the PRF, result in outBuf[] (at offset 'off', producing
+        * exactly 'len' bytes).
+        */
+       public void GetBytes(byte[] secret, byte[] label, byte[] seed,
+               byte[] outBuf, int off, int len)
+       {
+               for (int i = 0; i < len; i ++) {
+                       outBuf[off + i] = 0;
+               }
+               if (hm2 == null) {
+                       Phash(hm1, secret, 0, secret.Length,
+                               bufa1, bufb1,
+                               label, seed, outBuf, off, len);
+               } else {
+                       int n = (secret.Length + 1) >> 1;
+                       Phash(hm1, secret, 0, n,
+                               bufa1, bufb1,
+                               label, seed, outBuf, off, len);
+                       Phash(hm2, secret, secret.Length - n, n,
+                               bufa2, bufb2,
+                               label, seed, outBuf, off, len);
+               }
+       }
+
+       /*
+        * Compute the PRF, result is written in a newly allocated
+        * array (of length 'outLen' bytes).
+        */
+       public byte[] GetBytes(byte[] secret, byte[] label, byte[] seed,
+               int outLen)
+       {
+               byte[] r = new byte[outLen];
+               GetBytes(secret, label, seed, r, 0, outLen);
+               return r;
+       }
+
+       /*
+        * This function computes Phash with the specified HMAC
+        * engine, XORing the output with the current contents of
+        * the outBuf[] buffer.
+        */
+       static void Phash(HMAC hm, byte[] s, int soff, int slen,
+               byte[] bufa, byte[] bufb,
+               byte[] label, byte[] seed,
+               byte[] outBuf, int outOff, int outLen)
+       {
+               /*
+                * Set the key for HMAC.
+                */
+               hm.SetKey(s, soff, slen);
+
+               /*
+                * Compute A(1) = HMAC(secret, seed).
+                */
+               hm.Update(label);
+               hm.Update(seed);
+               hm.DoFinal(bufa, 0);
+               while (outLen > 0) {
+                       /*
+                        * Next chunk: HMAC(secret, A(i) + label + seed)
+                        */
+                       hm.Update(bufa);
+                       hm.Update(label);
+                       hm.Update(seed);
+                       hm.DoFinal(bufb, 0);
+                       int clen = Math.Min(hm.MACSize, outLen);
+                       for (int i = 0; i < clen; i ++) {
+                               outBuf[outOff ++] ^= bufb[i];
+                       }
+                       outLen -= clen;
+
+                       /*
+                        * If we are not finished, then compute:
+                        * A(i+1) = HMAC(secret, A(i))
+                        */
+                       if (outLen > 0) {
+                               hm.Update(bufa);
+                               hm.DoFinal(bufa, 0);
+                       }
+               }
+       }
+}
+
+}
diff --git a/SSLTLS/RecordDecrypt.cs b/SSLTLS/RecordDecrypt.cs
new file mode 100644 (file)
index 0000000..a1b1008
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+internal abstract class RecordDecrypt {
+
+       /*
+        * Check whether an incoming record length is valid.
+        */
+       internal abstract bool CheckLength(int len);
+
+       /*
+        * Decrypt the incoming record. This method assumes that the
+        * record length has already been tested with CheckLength().
+        * The 'off' and 'len' values point to the record contents (not
+        * including the header), and are adjusted to point at the
+        * decrypted plaintext. On error, false is returned.
+        */
+       internal abstract bool Decrypt(int recordType, int version,
+               byte[] data, ref int off, ref int len);
+}
+
+}
diff --git a/SSLTLS/RecordDecryptCBC.cs b/SSLTLS/RecordDecryptCBC.cs
new file mode 100644 (file)
index 0000000..347d062
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+internal class RecordDecryptCBC : RecordDecrypt {
+
+       IBlockCipher bc;
+       HMAC hm;
+       byte[] iv, ivTmp;
+       bool explicitIV;
+       ulong seq;
+       byte[] tmp1, tmp2;
+
+       internal RecordDecryptCBC(IBlockCipher bc, HMAC hm, byte[] iv)
+       {
+               this.bc = bc;
+               this.hm = hm;
+               this.iv = new byte[bc.BlockSize];
+               this.ivTmp = new byte[bc.BlockSize];
+               if (iv == null) {
+                       explicitIV = true;
+               } else {
+                       Array.Copy(iv, 0, this.iv, 0, iv.Length);
+                       explicitIV = false;
+               }
+               seq = 0;
+               tmp1 = new byte[Math.Max(13, hm.MACSize)];
+               tmp2 = new byte[Math.Max(13, hm.MACSize)];
+       }
+
+       internal override bool CheckLength(int len)
+       {
+               /*
+                * Record length (not counting the header) must be
+                * a multiple of the block size, and have enough room
+                * for the MAC and the padding-length byte. With
+                * TLS 1.1+, there must also be an explicit IV.
+                */
+               int blen = bc.BlockSize;
+               int hlen = hm.MACSize;
+               if ((len & (blen - 1)) != 0) {
+                       return false;
+               }
+               int minLen = hlen + 1;
+               int maxLen = (16384 + 256 + hlen) & ~(blen - 1);
+               if (explicitIV) {
+                       minLen += blen;
+                       maxLen += blen;
+               }
+               return len >= minLen && len <= maxLen;
+       }
+
+       internal override bool Decrypt(int recordType, int version,
+               byte[] data, ref int off, ref int len)
+       {
+               int blen = bc.BlockSize;
+               int hlen = hm.MACSize;
+
+               /*
+                * Grab a copy of the last encrypted block; this is
+                * the "saved IV" for the next record.
+                */
+               Array.Copy(data, off + len - blen, ivTmp, 0, blen);
+
+               /*
+                * Decrypt the data. The length has already been
+                * checked. If there is an explicit IV, it gets
+                * "decrypted" as well, which is not a problem.
+                */
+               bc.CBCDecrypt(iv, data, off, len);
+               Array.Copy(ivTmp, 0, iv, 0, blen);
+               if (explicitIV) {
+                       off += blen;
+                       len -= blen;
+               }
+
+               /*
+                * Compute minimum and maximum length of plaintext + MAC.
+                * These can be inferred from the observable record length,
+                * and thus are not secret.
+                */
+               int minLen = (hlen + 256 < len) ? len - 256 : hlen;
+               int maxLen = len - 1;
+
+               /*
+                * Get the actual padding length and check padding. The
+                * padding length must match the minLen/maxLen range.
+                */
+               int padLen = data[off + len - 1];
+               int good = ~(((maxLen - minLen) - padLen) >> 31);
+               int lenWithMAC = minLen ^ (good & (minLen ^ (maxLen - padLen)));
+               int dbb = 0;
+               for (int i = minLen; i < maxLen; i ++) {
+                       dbb |= ~((i - lenWithMAC) >> 31)
+                               & (data[off + i] ^ padLen);
+               }
+               good &= ~((dbb | -dbb) >> 31);
+
+               /*
+                * Extract the MAC value; this is done in one pass, but
+                * results in a "rotate" MAC value. The rotation count
+                * is kept in 'rotCount': this is the offset of the
+                * first MAC value byte in tmp1[].
+                */
+               int lenNoMAC = lenWithMAC - hlen;
+               minLen -= hlen;
+               int rotCount = 0;
+               for (int i = 0; i < hlen; i ++) {
+                       tmp1[i] = 0;
+               }
+               int v = 0;
+               for (int i = minLen; i < maxLen; i ++) {
+                       int m = ~((i - lenNoMAC) >> 31)
+                               & ((i - lenWithMAC) >> 31);
+                       tmp1[v] |= (byte)(m & data[off + i]);
+                       m = i - lenNoMAC;
+                       rotCount |= ~((m | -m) >> 31) & v;
+                       if (++ v == hlen) {
+                               v = 0;
+                       }
+               }
+               maxLen -= hlen;
+
+               /*
+                * Rotate back the MAC value. We do it bit by bit, with
+                * 6 iterations; this is good for all MAC value up to
+                * and including 64 bytes.
+                */
+               for (int i = 5; i >= 0; i --) {
+                       int rc = 1 << i;
+                       if (rc >= hlen) {
+                               continue;
+                       }
+                       int ctl = -((rotCount >> i) & 1);
+                       for (int j = 0, k = rc; j < hlen; j ++) {
+                               int b1 = tmp1[j];
+                               int b2 = tmp1[k];
+                               tmp2[j] = (byte)(b1 ^ (ctl & (b1 ^ b2)));
+                               if (++ k == hlen) {
+                                       k = 0;
+                               }
+                       }
+                       Array.Copy(tmp2, 0, tmp1, 0, hlen);
+                       rotCount &= ~rc;
+               }
+
+               /*
+                * Recompute the HMAC value. At that point, minLen and
+                * maxLen have been adjusted to match the plaintext
+                * without the MAC.
+                */
+               IO.Enc64be(seq ++, tmp2, 0);
+               IO.WriteHeader(recordType, version, lenNoMAC, tmp2, 8);
+               hm.Update(tmp2, 0, 13);
+               hm.ComputeCT(data, off, lenNoMAC, minLen, maxLen, tmp2, 0);
+
+               /*
+                * Compare MAC values.
+                */
+               dbb = 0;
+               for (int i = 0; i < hlen; i ++) {
+                       dbb |= tmp1[i] ^ tmp2[i];
+               }
+               good &= ~((dbb | -dbb) >> 31);
+
+               /*
+                * We must also check that the plaintext length fits in
+                * the maximum allowed by the standard (previous check
+                * was on the encrypted length).
+                */
+               good &= (lenNoMAC - 16385) >> 31;
+               len = lenNoMAC;
+               return good != 0;
+       }
+}
+
+}
diff --git a/SSLTLS/RecordDecryptChaPol.cs b/SSLTLS/RecordDecryptChaPol.cs
new file mode 100644 (file)
index 0000000..13be67e
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+internal class RecordDecryptChaPol : RecordDecrypt {
+
+       Poly1305 pp;
+       byte[] iv;
+       byte[] nonce;
+       byte[] tmp;
+       byte[] tag;
+       ulong seq;
+
+       internal RecordDecryptChaPol(Poly1305 pp, byte[] iv)
+       {
+               this.pp = pp;
+               this.iv = new byte[12];
+               Array.Copy(iv, 0, this.iv, 0, 12);
+               nonce = new byte[12];
+               tmp = new byte[13];
+               tag = new byte[16];
+               seq = 0;
+       }
+
+       internal override bool CheckLength(int len)
+       {
+               return len >= 16 && len <= (16384 + 16);
+       }
+
+       internal override bool Decrypt(int recordType, int version,
+               byte[] data, ref int off, ref int len)
+       {
+               /*
+                * Make the "additional data" for the MAC:
+                *  -- sequence number (8 bytes, big-endian)
+                *  -- header with plaintext length (5 bytes)
+                */
+               len -= 16;
+               IO.Enc64be(seq, tmp, 0);
+               IO.WriteHeader(recordType, version, len, tmp, 8);
+
+               /*
+                * The ChaCha20+Poly1305 IV consists in the
+                * implicit IV (12 bytes), with the sequence number
+                * "XORed" in the last 8 bytes (big-endian).
+                */
+               Array.Copy(iv, 0, nonce, 0, 12);
+               for (int i = 0; i < 8; i ++) {
+                       nonce[i + 4] ^= tmp[i];
+               }
+
+               /*
+                * Do encryption and compute tag.
+                */
+               pp.Run(nonce, data, off, len, tmp, 0, 13, tag, false);
+
+               /*
+                * Each record has its own sequence number.
+                */
+               seq ++;
+
+               /*
+                * Compare the tag value.
+                */
+               int z = 0;
+               for (int i = 0; i < 16; i ++) {
+                       z |= tag[i] ^ data[off + len + i];
+               }
+               return z == 0;
+       }
+}
+
+}
diff --git a/SSLTLS/RecordDecryptGCM.cs b/SSLTLS/RecordDecryptGCM.cs
new file mode 100644 (file)
index 0000000..7dbae8c
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+internal class RecordDecryptGCM : RecordDecrypt {
+
+       IBlockCipher bc;
+       byte[] iv;
+       byte[] h;
+       ulong seq;
+       byte[] tmp, tag;
+
+       internal RecordDecryptGCM(IBlockCipher bc, byte[] iv)
+       {
+               this.bc = bc;
+               this.iv = new byte[12];
+               Array.Copy(iv, 0, this.iv, 0, 4);
+               h = new byte[16];
+               bc.BlockEncrypt(h);
+               seq = 0;
+               tag = new byte[16];
+               tmp = new byte[29];
+       }
+
+       internal override bool CheckLength(int len)
+       {
+               return len >= 24 && len <= (16384 + 24);
+       }
+
+       internal override bool Decrypt(int recordType, int version,
+               byte[] data, ref int off, ref int len)
+       {
+               off += 8;
+               len -= 24;
+               IO.Enc64be(seq, tmp, 0);
+               IO.WriteHeader(recordType, version, len, tmp, 8);
+               IO.Enc64be(13 << 3, tmp, 13);
+               IO.Enc64be((ulong)len << 3, tmp, 21);
+               for (int i = 0; i < 16; i ++) {
+                       tag[i] = 0;
+               }
+               GHASH.Run(tag, h, tmp, 0, 13);
+               GHASH.Run(tag, h, data, off, len);
+               GHASH.Run(tag, h, tmp, 13, 16);
+               seq ++;
+
+               Array.Copy(data, off - 8, iv, 4, 8);
+               bc.CTRRun(iv, 2, data, off, len);
+               bc.CTRRun(iv, 1, data, off + len, 16);
+
+               int z = 0;
+               for (int i = 0; i < 16; i ++) {
+                       z |= tag[i] ^ data[off + len + i];
+               }
+               return z == 0;
+       }
+}
+
+}
diff --git a/SSLTLS/RecordDecryptPlain.cs b/SSLTLS/RecordDecryptPlain.cs
new file mode 100644 (file)
index 0000000..c20b6e8
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+internal class RecordDecryptPlain : RecordDecrypt {
+
+       internal override bool CheckLength(int len)
+       {
+               return len <= 16384;
+       }
+
+       internal override bool Decrypt(int recordType, int version,
+               byte[] data, ref int off, ref int len)
+       {
+               return true;
+       }
+}
+
+}
diff --git a/SSLTLS/RecordEncrypt.cs b/SSLTLS/RecordEncrypt.cs
new file mode 100644 (file)
index 0000000..af6c361
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+internal abstract class RecordEncrypt {
+
+       /*
+        * Adjust index for plaintext. This function must reserve room
+        * for header, MAC, padding...
+        */
+       internal abstract void GetMaxPlaintext(ref int start, ref int end);
+
+       /*
+        * Perform encryption. The plaintext offset and length are provided;
+        * upon exit, they are modified to designate the complete record
+        * (including the header).
+        */
+       internal abstract void Encrypt(int recordType, int version,
+               byte[] data, ref int off, ref int len);
+}
+
+}
diff --git a/SSLTLS/RecordEncryptCBC.cs b/SSLTLS/RecordEncryptCBC.cs
new file mode 100644 (file)
index 0000000..b0c55fe
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+internal class RecordEncryptCBC : RecordEncrypt {
+
+       IBlockCipher bc;
+       HMAC hm;
+       byte[] iv;
+       bool explicitIV;
+       ulong seq;
+       byte[] tmp;
+
+       internal RecordEncryptCBC(IBlockCipher bc, HMAC hm, byte[] iv)
+       {
+               this.bc = bc;
+               this.hm = hm;
+               this.iv = new byte[bc.BlockSize];
+               if (iv == null) {
+                       explicitIV = true;
+               } else {
+                       Array.Copy(iv, 0, this.iv, 0, iv.Length);
+                       explicitIV = false;
+               }
+               seq = 0;
+               tmp = new byte[Math.Max(13, hm.MACSize)];
+       }
+
+       internal override void GetMaxPlaintext(ref int start, ref int end)
+       {
+               /*
+                * Add room for the record header.
+                */
+               start += 5;
+
+               int blen = bc.BlockSize;
+               if (explicitIV) {
+                       start += blen;
+               } else {
+                       /*
+                        * We reserve room for an automatic 1/n-1 split.
+                        */
+                       start += 4 + ((hm.MACSize + blen + 1) & ~(blen - 1));
+               }
+               int len = (end - start) & ~(blen - 1);
+               len -= 1 + hm.MACSize;
+
+               /*
+                * We keep a bit of extra room to try out overlong padding.
+                */
+               len -= blen;
+
+               if (len > 16384) {
+                       len = 16384;
+               }
+               end = start + len;
+       }
+
+       internal override void Encrypt(int recordType, int version,
+               byte[] data, ref int off, ref int len)
+       {
+               if (explicitIV
+                       || recordType != SSL.APPLICATION_DATA
+                       || len <= 1)
+               {
+                       EncryptInner(recordType, version,
+                               data, ref off, ref len);
+                       return;
+               }
+
+               /*
+                * Automatic 1/n-1 split. We do it only when there is
+                * no explicit IV (i.e. TLS 1.0, not TLS 1.1+), and there
+                * are at least two plaintext bytes in the record.
+                */
+               int blen = bc.BlockSize;
+               int off1 = off - (4 + ((hm.MACSize + blen + 1) & ~(blen - 1)));
+               int len1 = 1;
+               data[off1] = data[off];
+               EncryptInner(recordType, version, data, ref off1, ref len1);
+               int off2 = off + 1;
+               int len2 = len - 1;
+               EncryptInner(recordType, version, data, ref off2, ref len2);
+               if (off1 + len1 != off2) {
+                       throw new Exception("Split gone wrong");
+               }
+               off = off1;
+               len = len1 + len2;
+       }
+
+       void EncryptInner(int recordType, int version,
+               byte[] data, ref int off, ref int len)
+       {
+               int blen = bc.BlockSize;
+               int mlen = hm.MACSize;
+               int doff = off;
+               int dlen = len;
+
+               if (explicitIV) {
+                       /*
+                        * To make pseudorandom IV, we reuse HMAC, computed
+                        * over the encoded sequence number. Since this
+                        * input is distinct from all other HMAC inputs with
+                        * the same key, this should be randomish enough
+                        * (assuming HMAC is a good imitation of a random
+                        * oracle).
+                        */
+                       IO.Enc64be(seq, tmp, 0);
+                       hm.Update(tmp, 0, 8);
+                       hm.DoFinal(tmp, 0);
+                       Array.Copy(tmp, 0, data, off - blen, blen);
+                       off -= blen;
+                       len += blen;
+               }
+
+               /*
+                * Compute HMAC.
+                */
+               IO.Enc64be(seq, tmp, 0);
+               IO.WriteHeader(recordType, version, dlen, tmp, 8);
+               hm.Update(tmp, 0, 13);
+               hm.Update(data, doff, dlen);
+               hm.DoFinal(data, off + len);
+               len += mlen;
+               seq ++;
+
+               /*
+                * Add padding.
+                */
+               int plen = blen - (len & (blen - 1));
+               for (int i = 0; i < plen; i ++) {
+                       data[off + len + i] = (byte)(plen - 1);
+               }
+               len += plen;
+
+               /*
+                * Perform CBC encryption. We use our saved IV. If there is
+                * an explicit IV, then it gets encrypted, which is fine
+                * (CBC encryption of randomness is equally good randomness).
+                */
+               bc.CBCEncrypt(iv, data, off, len);
+               Array.Copy(data, off + len - blen, iv, 0, blen);
+
+               /*
+                * Add the header.
+                */
+               off -= 5;
+               IO.WriteHeader(recordType, version, len, data, off);
+               len += 5;
+       }
+}
+
+}
diff --git a/SSLTLS/RecordEncryptChaPol.cs b/SSLTLS/RecordEncryptChaPol.cs
new file mode 100644 (file)
index 0000000..ead61dd
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+internal class RecordEncryptChaPol : RecordEncrypt {
+
+       Poly1305 pp;
+       byte[] iv;
+       byte[] nonce;
+       byte[] tmp;
+       byte[] tag;
+       ulong seq;
+
+       internal RecordEncryptChaPol(Poly1305 pp, byte[] iv)
+       {
+               this.pp = pp;
+               this.iv = new byte[12];
+               Array.Copy(iv, 0, this.iv, 0, 12);
+               nonce = new byte[12];
+               tmp = new byte[13];
+               tag = new byte[16];
+               seq = 0;
+       }
+
+       internal override void GetMaxPlaintext(ref int start, ref int end)
+       {
+               /*
+                * We need room at the start for the record header (5 bytes)
+                * and some at the end for the MAC (16 bytes).
+                */
+               start += 5;
+               end -= 16;
+               int len = Math.Min(end - start, 16384);
+               end = start + len;
+       }
+
+       internal override void Encrypt(int recordType, int version,
+               byte[] data, ref int off, ref int len)
+       {
+               /*
+                * Make the "additional data" for the MAC:
+                *  -- sequence number (8 bytes, big-endian)
+                *  -- header with plaintext length (5 bytes)
+                */
+               IO.Enc64be(seq, tmp, 0);
+               IO.WriteHeader(recordType, version, len, tmp, 8);
+
+               /*
+                * The ChaCha20+Poly1305 IV consists in the
+                * implicit IV (12 bytes), with the sequence number
+                * "XORed" in the last 8 bytes (big-endian).
+                */
+               Array.Copy(iv, 0, nonce, 0, 12);
+               for (int i = 0; i < 8; i ++) {
+                       nonce[i + 4] ^= tmp[i];
+               }
+
+               /*
+                * Do encryption and compute tag.
+                */
+               pp.Run(nonce, data, off, len, tmp, 0, 13, tag, true);
+
+               /*
+                * Copy back tag where appropriate and add header.
+                */
+               Array.Copy(tag, 0, data, off + len, 16);
+               off -= 5;
+               len += 16;
+               IO.WriteHeader(recordType, version, len, data, off);
+               len += 5;
+
+               /*
+                * Each record has its own sequence number.
+                */
+               seq ++;
+       }
+}
+
+}
diff --git a/SSLTLS/RecordEncryptGCM.cs b/SSLTLS/RecordEncryptGCM.cs
new file mode 100644 (file)
index 0000000..2255155
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+internal class RecordEncryptGCM : RecordEncrypt {
+
+       IBlockCipher bc;
+       byte[] iv;
+       byte[] h;
+       ulong seq;
+       byte[] tmp, tag;
+
+       internal RecordEncryptGCM(IBlockCipher bc, byte[] iv)
+       {
+               this.bc = bc;
+               this.iv = new byte[12];
+               Array.Copy(iv, 0, this.iv, 0, 4);
+               h = new byte[16];
+               bc.BlockEncrypt(h);
+               seq = 0;
+               tag = new byte[16];
+               tmp = new byte[29];
+       }
+
+       internal override void GetMaxPlaintext(ref int start, ref int end)
+       {
+               /*
+                * We need room at the start for the record header (5 bytes)
+                * and the explicit nonce (8 bytes). We need room at the end
+                * for the MAC (16 bytes).
+                */
+               start += 13;
+               end -= 16;
+               int len = Math.Min(end - start, 16384);
+               end = start + len;
+       }
+
+       internal override void Encrypt(int recordType, int version,
+               byte[] data, ref int off, ref int len)
+       {
+               /*
+                * Explicit nonce is the encoded sequence number. We
+                * encrypt the data itself; the counter starts at 2
+                * (value 1 is for the authentication tag).
+                */
+               IO.Enc64be(seq, data, off - 8);
+               Array.Copy(data, off - 8, iv, 4, 8);
+               bc.CTRRun(iv, 2, data, off, len);
+
+               /*
+                * For the authentication tag:
+                *   header = sequence + 5-byte "plain" header
+                *   footer = the two relevant lengths (in bits)
+                */
+               IO.Enc64be(seq, tmp, 0);
+               IO.WriteHeader(recordType, version, len, tmp, 8);
+               IO.Enc64be(13 << 3, tmp, 13);
+               IO.Enc64be((ulong)len << 3, tmp, 21);
+
+               /*
+                * Compute clear authentication tag.
+                */
+               for (int i = 0; i < 16; i ++) {
+                       tag[i] = 0;
+               }
+               GHASH.Run(tag, h, tmp, 0, 13);
+               GHASH.Run(tag, h, data, off, len);
+               GHASH.Run(tag, h, tmp, 13, 16);
+
+               /*
+                * Copy authentication tag and apply final encryption on it.
+                */
+               Array.Copy(tag, 0, data, off + len, 16);
+               bc.CTRRun(iv, 1, data, off + len, 16);
+
+               /*
+                * Each record uses one sequence number.
+                */
+               seq ++;
+
+               /*
+                * Write encrypted header and return adjusted offset/length.
+                */
+               off -= 13;
+               len += 24;
+               IO.WriteHeader(recordType, version, len, data, off);
+               len += 5;
+       }
+}
+
+}
diff --git a/SSLTLS/RecordEncryptPlain.cs b/SSLTLS/RecordEncryptPlain.cs
new file mode 100644 (file)
index 0000000..3a87802
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+internal class RecordEncryptPlain : RecordEncrypt {
+
+       internal RecordEncryptPlain()
+       {
+       }
+
+       internal override void GetMaxPlaintext(ref int start, ref int end)
+       {
+               start += 5;
+               end = Math.Min(end, start + 16384);
+       }
+
+       internal override void Encrypt(int recordType, int version,
+               byte[] data, ref int off, ref int len)
+       {
+               off -= 5;
+               IO.WriteHeader(recordType, version, len, data, off);
+               len += 5;
+       }
+}
+
+}
diff --git a/SSLTLS/SSL.cs b/SSLTLS/SSL.cs
new file mode 100644 (file)
index 0000000..5250e5d
--- /dev/null
@@ -0,0 +1,859 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+
+using Asn1;
+using Crypto;
+using XKeys;
+
+namespace SSLTLS {
+
+/*
+ * A fake class that serves as container for various constants.
+ */
+
+public sealed class SSL {
+
+       /*
+        * Protocol versions.
+        */
+       public const int SSL30 = 0x0300;
+       public const int TLS10 = 0x0301;
+       public const int TLS11 = 0x0302;
+       public const int TLS12 = 0x0303;
+
+       /*
+        * Record types.
+        */
+       public const int CHANGE_CIPHER_SPEC  = 20;
+       public const int ALERT               = 21;
+       public const int HANDSHAKE           = 22;
+       public const int APPLICATION_DATA    = 23;
+
+       /*
+        * Alert levels.
+        */
+       public const int WARNING  = 1;
+       public const int FATAL    = 2;
+
+       /*
+        * Alert messages.
+        */
+       public const int CLOSE_NOTIFY             = 0;
+       public const int UNEXPECTED_MESSAGE       = 10;
+       public const int BAD_RECORD_MAC           = 20;
+       public const int DECRYPTION_FAILED        = 21;
+       public const int RECORD_OVERFLOW          = 22;
+       public const int DECOMPRESSION_FAILURE    = 30;
+       public const int HANDSHAKE_FAILURE        = 40;
+       public const int BAD_CERTIFICATE          = 42;
+       public const int UNSUPPORTED_CERTIFICATE  = 43;
+       public const int CERTIFICATE_REVOKED      = 44;
+       public const int CERTIFICATE_EXPIRED      = 45;
+       public const int CERTIFICATE_UNKNOWN      = 46;
+       public const int ILLEGAL_PARAMETER        = 47;
+       public const int UNKNOWN_CA               = 48;
+       public const int ACCESS_DENIED            = 49;
+       public const int DECODE_ERROR             = 50;
+       public const int DECRYPT_ERROR            = 51;
+       public const int EXPORT_RESTRICTION       = 60;
+       public const int PROTOCOL_VERSION         = 70;
+       public const int INSUFFICIENT_SECURITY    = 71;
+       public const int INTERNAL_ERROR           = 80;
+       public const int USER_CANCELED            = 90;
+       public const int NO_RENEGOTIATION         = 100;
+
+       /*
+        * Handshake message types.
+        */
+       public const int HELLO_REQUEST        = 0;
+       public const int CLIENT_HELLO         = 1;
+       public const int SERVER_HELLO         = 2;
+       public const int CERTIFICATE          = 11;
+       public const int SERVER_KEY_EXCHANGE  = 12;
+       public const int CERTIFICATE_REQUEST  = 13;
+       public const int SERVER_HELLO_DONE    = 14;
+       public const int CERTIFICATE_VERIFY   = 15;
+       public const int CLIENT_KEY_EXCHANGE  = 16;
+       public const int FINISHED             = 20;
+
+       /*
+        * Cipher suites.
+        */
+
+       /* From RFC 5246 */
+       public const int NULL_WITH_NULL_NULL                    = 0x0000;
+       public const int RSA_WITH_NULL_MD5                      = 0x0001;
+       public const int RSA_WITH_NULL_SHA                      = 0x0002;
+       public const int RSA_WITH_NULL_SHA256                   = 0x003B;
+       public const int RSA_WITH_RC4_128_MD5                   = 0x0004;
+       public const int RSA_WITH_RC4_128_SHA                   = 0x0005;
+       public const int RSA_WITH_3DES_EDE_CBC_SHA              = 0x000A;
+       public const int RSA_WITH_AES_128_CBC_SHA               = 0x002F;
+       public const int RSA_WITH_AES_256_CBC_SHA               = 0x0035;
+       public const int RSA_WITH_AES_128_CBC_SHA256            = 0x003C;
+       public const int RSA_WITH_AES_256_CBC_SHA256            = 0x003D;
+       public const int DH_DSS_WITH_3DES_EDE_CBC_SHA           = 0x000D;
+       public const int DH_RSA_WITH_3DES_EDE_CBC_SHA           = 0x0010;
+       public const int DHE_DSS_WITH_3DES_EDE_CBC_SHA          = 0x0013;
+       public const int DHE_RSA_WITH_3DES_EDE_CBC_SHA          = 0x0016;
+       public const int DH_DSS_WITH_AES_128_CBC_SHA            = 0x0030;
+       public const int DH_RSA_WITH_AES_128_CBC_SHA            = 0x0031;
+       public const int DHE_DSS_WITH_AES_128_CBC_SHA           = 0x0032;
+       public const int DHE_RSA_WITH_AES_128_CBC_SHA           = 0x0033;
+       public const int DH_DSS_WITH_AES_256_CBC_SHA            = 0x0036;
+       public const int DH_RSA_WITH_AES_256_CBC_SHA            = 0x0037;
+       public const int DHE_DSS_WITH_AES_256_CBC_SHA           = 0x0038;
+       public const int DHE_RSA_WITH_AES_256_CBC_SHA           = 0x0039;
+       public const int DH_DSS_WITH_AES_128_CBC_SHA256         = 0x003E;
+       public const int DH_RSA_WITH_AES_128_CBC_SHA256         = 0x003F;
+       public const int DHE_DSS_WITH_AES_128_CBC_SHA256        = 0x0040;
+       public const int DHE_RSA_WITH_AES_128_CBC_SHA256        = 0x0067;
+       public const int DH_DSS_WITH_AES_256_CBC_SHA256         = 0x0068;
+       public const int DH_RSA_WITH_AES_256_CBC_SHA256         = 0x0069;
+       public const int DHE_DSS_WITH_AES_256_CBC_SHA256        = 0x006A;
+       public const int DHE_RSA_WITH_AES_256_CBC_SHA256        = 0x006B;
+       public const int DH_anon_WITH_RC4_128_MD5               = 0x0018;
+       public const int DH_anon_WITH_3DES_EDE_CBC_SHA          = 0x001B;
+       public const int DH_anon_WITH_AES_128_CBC_SHA           = 0x0034;
+       public const int DH_anon_WITH_AES_256_CBC_SHA           = 0x003A;
+       public const int DH_anon_WITH_AES_128_CBC_SHA256        = 0x006C;
+       public const int DH_anon_WITH_AES_256_CBC_SHA256        = 0x006D;
+
+       /* From RFC 4492 */
+       public const int ECDH_ECDSA_WITH_NULL_SHA               = 0xC001;
+       public const int ECDH_ECDSA_WITH_RC4_128_SHA            = 0xC002;
+       public const int ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA       = 0xC003;
+       public const int ECDH_ECDSA_WITH_AES_128_CBC_SHA        = 0xC004;
+       public const int ECDH_ECDSA_WITH_AES_256_CBC_SHA        = 0xC005;
+       public const int ECDHE_ECDSA_WITH_NULL_SHA              = 0xC006;
+       public const int ECDHE_ECDSA_WITH_RC4_128_SHA           = 0xC007;
+       public const int ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA      = 0xC008;
+       public const int ECDHE_ECDSA_WITH_AES_128_CBC_SHA       = 0xC009;
+       public const int ECDHE_ECDSA_WITH_AES_256_CBC_SHA       = 0xC00A;
+       public const int ECDH_RSA_WITH_NULL_SHA                 = 0xC00B;
+       public const int ECDH_RSA_WITH_RC4_128_SHA              = 0xC00C;
+       public const int ECDH_RSA_WITH_3DES_EDE_CBC_SHA         = 0xC00D;
+       public const int ECDH_RSA_WITH_AES_128_CBC_SHA          = 0xC00E;
+       public const int ECDH_RSA_WITH_AES_256_CBC_SHA          = 0xC00F;
+       public const int ECDHE_RSA_WITH_NULL_SHA                = 0xC010;
+       public const int ECDHE_RSA_WITH_RC4_128_SHA             = 0xC011;
+       public const int ECDHE_RSA_WITH_3DES_EDE_CBC_SHA        = 0xC012;
+       public const int ECDHE_RSA_WITH_AES_128_CBC_SHA         = 0xC013;
+       public const int ECDHE_RSA_WITH_AES_256_CBC_SHA         = 0xC014;
+       public const int ECDH_anon_WITH_NULL_SHA                = 0xC015;
+       public const int ECDH_anon_WITH_RC4_128_SHA             = 0xC016;
+       public const int ECDH_anon_WITH_3DES_EDE_CBC_SHA        = 0xC017;
+       public const int ECDH_anon_WITH_AES_128_CBC_SHA         = 0xC018;
+       public const int ECDH_anon_WITH_AES_256_CBC_SHA         = 0xC019;
+
+       /* From RFC 5288 */
+       public const int RSA_WITH_AES_128_GCM_SHA256            = 0x009C;
+       public const int RSA_WITH_AES_256_GCM_SHA384            = 0x009D;
+       public const int DHE_RSA_WITH_AES_128_GCM_SHA256        = 0x009E;
+       public const int DHE_RSA_WITH_AES_256_GCM_SHA384        = 0x009F;
+       public const int DH_RSA_WITH_AES_128_GCM_SHA256         = 0x00A0;
+       public const int DH_RSA_WITH_AES_256_GCM_SHA384         = 0x00A1;
+       public const int DHE_DSS_WITH_AES_128_GCM_SHA256        = 0x00A2;
+       public const int DHE_DSS_WITH_AES_256_GCM_SHA384        = 0x00A3;
+       public const int DH_DSS_WITH_AES_128_GCM_SHA256         = 0x00A4;
+       public const int DH_DSS_WITH_AES_256_GCM_SHA384         = 0x00A5;
+       public const int DH_anon_WITH_AES_128_GCM_SHA256        = 0x00A6;
+       public const int DH_anon_WITH_AES_256_GCM_SHA384        = 0x00A7;
+
+       /* From RFC 5289 */
+       public const int ECDHE_ECDSA_WITH_AES_128_CBC_SHA256    = 0xC023;
+       public const int ECDHE_ECDSA_WITH_AES_256_CBC_SHA384    = 0xC024;
+       public const int ECDH_ECDSA_WITH_AES_128_CBC_SHA256     = 0xC025;
+       public const int ECDH_ECDSA_WITH_AES_256_CBC_SHA384     = 0xC026;
+       public const int ECDHE_RSA_WITH_AES_128_CBC_SHA256      = 0xC027;
+       public const int ECDHE_RSA_WITH_AES_256_CBC_SHA384      = 0xC028;
+       public const int ECDH_RSA_WITH_AES_128_CBC_SHA256       = 0xC029;
+       public const int ECDH_RSA_WITH_AES_256_CBC_SHA384       = 0xC02A;
+       public const int ECDHE_ECDSA_WITH_AES_128_GCM_SHA256    = 0xC02B;
+       public const int ECDHE_ECDSA_WITH_AES_256_GCM_SHA384    = 0xC02C;
+       public const int ECDH_ECDSA_WITH_AES_128_GCM_SHA256     = 0xC02D;
+       public const int ECDH_ECDSA_WITH_AES_256_GCM_SHA384     = 0xC02E;
+       public const int ECDHE_RSA_WITH_AES_128_GCM_SHA256      = 0xC02F;
+       public const int ECDHE_RSA_WITH_AES_256_GCM_SHA384      = 0xC030;
+       public const int ECDH_RSA_WITH_AES_128_GCM_SHA256       = 0xC031;
+       public const int ECDH_RSA_WITH_AES_256_GCM_SHA384       = 0xC032;
+
+       /* From RFC 7905 */
+       public const int ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256      = 0xCCA8;
+       public const int ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256    = 0xCCA9;
+       public const int DHE_RSA_WITH_CHACHA20_POLY1305_SHA256        = 0xCCAA;
+       public const int PSK_WITH_CHACHA20_POLY1305_SHA256            = 0xCCAB;
+       public const int ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256      = 0xCCAC;
+       public const int DHE_PSK_WITH_CHACHA20_POLY1305_SHA256        = 0xCCAD;
+       public const int RSA_PSK_WITH_CHACHA20_POLY1305_SHA256        = 0xCCAE;
+
+       /* From RFC 7507 */
+       public const int FALLBACK_SCSV                          = 0x5600;
+
+       /* From RFC 5746 */
+       public const int EMPTY_RENEGOTIATION_INFO_SCSV          = 0x00FF;
+
+       /*
+        * Client certificate types.
+        */
+       public const int RSA_SIGN      = 1;
+       public const int DSS_SIGN      = 2;
+       public const int RSA_FIXED_DH  = 3;
+       public const int DSS_FIXED_DH  = 4;
+
+       /*
+        * Hash algorithm identifiers. The special "MD5SHA1" is for use
+        * with RSA signatures in TLS 1.0 and 1.1 only.
+        */
+       public const int MD5SHA1 = 0;
+       public const int MD5     = 1;
+       public const int SHA1    = 2;
+       public const int SHA224  = 3;
+       public const int SHA256  = 4;
+       public const int SHA384  = 5;
+       public const int SHA512  = 6;
+
+       /*
+        * Signature algorithm identifiers.
+        */
+       public const int RSA     = 1;
+       public const int DSA     = 2;
+       public const int ECDSA   = 3;
+
+       /*
+        * Combined hash-and-sign algorithms.
+        */
+       public const int RSA_MD5SHA1   = (MD5SHA1 << 8) + RSA;
+       public const int RSA_MD5       = (MD5 << 8) + RSA;
+       public const int RSA_SHA1      = (SHA1 << 8) + RSA;
+       public const int RSA_SHA224    = (SHA224 << 8) + RSA;
+       public const int RSA_SHA256    = (SHA256 << 8) + RSA;
+       public const int RSA_SHA384    = (SHA384 << 8) + RSA;
+       public const int RSA_SHA512    = (SHA512 << 8) + RSA;
+       public const int ECDSA_MD5     = (MD5 << 8) + ECDSA;
+       public const int ECDSA_SHA1    = (SHA1 << 8) + ECDSA;
+       public const int ECDSA_SHA224  = (SHA224 << 8) + ECDSA;
+       public const int ECDSA_SHA256  = (SHA256 << 8) + ECDSA;
+       public const int ECDSA_SHA384  = (SHA384 << 8) + ECDSA;
+       public const int ECDSA_SHA512  = (SHA512 << 8) + ECDSA;
+
+       /*
+        * Symbolic identifiers for named curves.
+        */
+       public const int NIST_P256   = 23;
+       public const int NIST_P384   = 24;
+       public const int NIST_P521   = 25;
+       public const int Curve25519  = 29;
+
+       /*
+        * Get a human-readable name for a version.
+        */
+       public static string VersionName(int version)
+       {
+               switch (version) {
+               case SSL30: return "SSL 3.0";
+               case TLS10: return "TLS 1.0";
+               case TLS11: return "TLS 1.1";
+               case TLS12: return "TLS 1.2";
+               }
+               if ((version >> 8) == 3) {
+                       return String.Format("TLS 1.{0}", (version & 0xFF) - 1);
+               }
+               return String.Format("UNKNOWN:0x{0:X4}", version);
+       }
+
+       /*
+        * Get a human-readable name for a cipher suite.
+        */
+       public static string CipherSuiteName(int cipherSuite)
+       {
+               switch (cipherSuite) {
+               case NULL_WITH_NULL_NULL:
+                       return "NULL_WITH_NULL_NULL";
+               case RSA_WITH_NULL_MD5:
+                       return "RSA_WITH_NULL_MD5";
+               case RSA_WITH_NULL_SHA:
+                       return "RSA_WITH_NULL_SHA";
+               case RSA_WITH_NULL_SHA256:
+                       return "RSA_WITH_NULL_SHA256";
+               case RSA_WITH_RC4_128_MD5:
+                       return "RSA_WITH_RC4_128_MD5";
+               case RSA_WITH_RC4_128_SHA:
+                       return "RSA_WITH_RC4_128_SHA";
+               case RSA_WITH_3DES_EDE_CBC_SHA:
+                       return "RSA_WITH_3DES_EDE_CBC_SHA";
+               case RSA_WITH_AES_128_CBC_SHA:
+                       return "RSA_WITH_AES_128_CBC_SHA";
+               case RSA_WITH_AES_256_CBC_SHA:
+                       return "RSA_WITH_AES_256_CBC_SHA";
+               case RSA_WITH_AES_128_CBC_SHA256:
+                       return "RSA_WITH_AES_128_CBC_SHA256";
+               case RSA_WITH_AES_256_CBC_SHA256:
+                       return "RSA_WITH_AES_256_CBC_SHA256";
+               case DH_DSS_WITH_3DES_EDE_CBC_SHA:
+                       return "DH_DSS_WITH_3DES_EDE_CBC_SHA";
+               case DH_RSA_WITH_3DES_EDE_CBC_SHA:
+                       return "DH_RSA_WITH_3DES_EDE_CBC_SHA";
+               case DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+                       return "DHE_DSS_WITH_3DES_EDE_CBC_SHA";
+               case DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+                       return "DHE_RSA_WITH_3DES_EDE_CBC_SHA";
+               case DH_DSS_WITH_AES_128_CBC_SHA:
+                       return "DH_DSS_WITH_AES_128_CBC_SHA";
+               case DH_RSA_WITH_AES_128_CBC_SHA:
+                       return "DH_RSA_WITH_AES_128_CBC_SHA";
+               case DHE_DSS_WITH_AES_128_CBC_SHA:
+                       return "DHE_DSS_WITH_AES_128_CBC_SHA";
+               case DHE_RSA_WITH_AES_128_CBC_SHA:
+                       return "DHE_RSA_WITH_AES_128_CBC_SHA";
+               case DH_DSS_WITH_AES_256_CBC_SHA:
+                       return "DH_DSS_WITH_AES_256_CBC_SHA";
+               case DH_RSA_WITH_AES_256_CBC_SHA:
+                       return "DH_RSA_WITH_AES_256_CBC_SHA";
+               case DHE_DSS_WITH_AES_256_CBC_SHA:
+                       return "DHE_DSS_WITH_AES_256_CBC_SHA";
+               case DHE_RSA_WITH_AES_256_CBC_SHA:
+                       return "DHE_RSA_WITH_AES_256_CBC_SHA";
+               case DH_DSS_WITH_AES_128_CBC_SHA256:
+                       return "DH_DSS_WITH_AES_128_CBC_SHA256";
+               case DH_RSA_WITH_AES_128_CBC_SHA256:
+                       return "DH_RSA_WITH_AES_128_CBC_SHA256";
+               case DHE_DSS_WITH_AES_128_CBC_SHA256:
+                       return "DHE_DSS_WITH_AES_128_CBC_SHA256";
+               case DHE_RSA_WITH_AES_128_CBC_SHA256:
+                       return "DHE_RSA_WITH_AES_128_CBC_SHA256";
+               case DH_DSS_WITH_AES_256_CBC_SHA256:
+                       return "DH_DSS_WITH_AES_256_CBC_SHA256";
+               case DH_RSA_WITH_AES_256_CBC_SHA256:
+                       return "DH_RSA_WITH_AES_256_CBC_SHA256";
+               case DHE_DSS_WITH_AES_256_CBC_SHA256:
+                       return "DHE_DSS_WITH_AES_256_CBC_SHA256";
+               case DHE_RSA_WITH_AES_256_CBC_SHA256:
+                       return "DHE_RSA_WITH_AES_256_CBC_SHA256";
+               case DH_anon_WITH_RC4_128_MD5:
+                       return "DH_anon_WITH_RC4_128_MD5";
+               case DH_anon_WITH_3DES_EDE_CBC_SHA:
+                       return "DH_anon_WITH_3DES_EDE_CBC_SHA";
+               case DH_anon_WITH_AES_128_CBC_SHA:
+                       return "DH_anon_WITH_AES_128_CBC_SHA";
+               case DH_anon_WITH_AES_256_CBC_SHA:
+                       return "DH_anon_WITH_AES_256_CBC_SHA";
+               case DH_anon_WITH_AES_128_CBC_SHA256:
+                       return "DH_anon_WITH_AES_128_CBC_SHA256";
+               case DH_anon_WITH_AES_256_CBC_SHA256:
+                       return "DH_anon_WITH_AES_256_CBC_SHA256";
+               case ECDH_ECDSA_WITH_NULL_SHA:
+                       return "ECDH_ECDSA_WITH_NULL_SHA";
+               case ECDH_ECDSA_WITH_RC4_128_SHA:
+                       return "ECDH_ECDSA_WITH_RC4_128_SHA";
+               case ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+                       return "ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA";
+               case ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+                       return "ECDH_ECDSA_WITH_AES_128_CBC_SHA";
+               case ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+                       return "ECDH_ECDSA_WITH_AES_256_CBC_SHA";
+               case ECDHE_ECDSA_WITH_NULL_SHA:
+                       return "ECDHE_ECDSA_WITH_NULL_SHA";
+               case ECDHE_ECDSA_WITH_RC4_128_SHA:
+                       return "ECDHE_ECDSA_WITH_RC4_128_SHA";
+               case ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+                       return "ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA";
+               case ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+                       return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA";
+               case ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+                       return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA";
+               case ECDH_RSA_WITH_NULL_SHA:
+                       return "ECDH_RSA_WITH_NULL_SHA";
+               case ECDH_RSA_WITH_RC4_128_SHA:
+                       return "ECDH_RSA_WITH_RC4_128_SHA";
+               case ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+                       return "ECDH_RSA_WITH_3DES_EDE_CBC_SHA";
+               case ECDH_RSA_WITH_AES_128_CBC_SHA:
+                       return "ECDH_RSA_WITH_AES_128_CBC_SHA";
+               case ECDH_RSA_WITH_AES_256_CBC_SHA:
+                       return "ECDH_RSA_WITH_AES_256_CBC_SHA";
+               case ECDHE_RSA_WITH_NULL_SHA:
+                       return "ECDHE_RSA_WITH_NULL_SHA";
+               case ECDHE_RSA_WITH_RC4_128_SHA:
+                       return "ECDHE_RSA_WITH_RC4_128_SHA";
+               case ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+                       return "ECDHE_RSA_WITH_3DES_EDE_CBC_SHA";
+               case ECDHE_RSA_WITH_AES_128_CBC_SHA:
+                       return "ECDHE_RSA_WITH_AES_128_CBC_SHA";
+               case ECDHE_RSA_WITH_AES_256_CBC_SHA:
+                       return "ECDHE_RSA_WITH_AES_256_CBC_SHA";
+               case ECDH_anon_WITH_NULL_SHA:
+                       return "ECDH_anon_WITH_NULL_SHA";
+               case ECDH_anon_WITH_RC4_128_SHA:
+                       return "ECDH_anon_WITH_RC4_128_SHA";
+               case ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+                       return "ECDH_anon_WITH_3DES_EDE_CBC_SHA";
+               case ECDH_anon_WITH_AES_128_CBC_SHA:
+                       return "ECDH_anon_WITH_AES_128_CBC_SHA";
+               case ECDH_anon_WITH_AES_256_CBC_SHA:
+                       return "ECDH_anon_WITH_AES_256_CBC_SHA";
+               case RSA_WITH_AES_128_GCM_SHA256:
+                       return "RSA_WITH_AES_128_GCM_SHA256";
+               case RSA_WITH_AES_256_GCM_SHA384:
+                       return "RSA_WITH_AES_256_GCM_SHA384";
+               case DHE_RSA_WITH_AES_128_GCM_SHA256:
+                       return "DHE_RSA_WITH_AES_128_GCM_SHA256";
+               case DHE_RSA_WITH_AES_256_GCM_SHA384:
+                       return "DHE_RSA_WITH_AES_256_GCM_SHA384";
+               case DH_RSA_WITH_AES_128_GCM_SHA256:
+                       return "DH_RSA_WITH_AES_128_GCM_SHA256";
+               case DH_RSA_WITH_AES_256_GCM_SHA384:
+                       return "DH_RSA_WITH_AES_256_GCM_SHA384";
+               case DHE_DSS_WITH_AES_128_GCM_SHA256:
+                       return "DHE_DSS_WITH_AES_128_GCM_SHA256";
+               case DHE_DSS_WITH_AES_256_GCM_SHA384:
+                       return "DHE_DSS_WITH_AES_256_GCM_SHA384";
+               case DH_DSS_WITH_AES_128_GCM_SHA256:
+                       return "DH_DSS_WITH_AES_128_GCM_SHA256";
+               case DH_DSS_WITH_AES_256_GCM_SHA384:
+                       return "DH_DSS_WITH_AES_256_GCM_SHA384";
+               case DH_anon_WITH_AES_128_GCM_SHA256:
+                       return "DH_anon_WITH_AES_128_GCM_SHA256";
+               case DH_anon_WITH_AES_256_GCM_SHA384:
+                       return "DH_anon_WITH_AES_256_GCM_SHA384";
+               case ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+                       return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256";
+               case ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+                       return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA384";
+               case ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+                       return "ECDH_ECDSA_WITH_AES_128_CBC_SHA256";
+               case ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+                       return "ECDH_ECDSA_WITH_AES_256_CBC_SHA384";
+               case ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+                       return "ECDHE_RSA_WITH_AES_128_CBC_SHA256";
+               case ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+                       return "ECDHE_RSA_WITH_AES_256_CBC_SHA384";
+               case ECDH_RSA_WITH_AES_128_CBC_SHA256:
+                       return "ECDH_RSA_WITH_AES_128_CBC_SHA256";
+               case ECDH_RSA_WITH_AES_256_CBC_SHA384:
+                       return "ECDH_RSA_WITH_AES_256_CBC_SHA384";
+               case ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+                       return "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256";
+               case ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+                       return "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384";
+               case ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+                       return "ECDH_ECDSA_WITH_AES_128_GCM_SHA256";
+               case ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+                       return "ECDH_ECDSA_WITH_AES_256_GCM_SHA384";
+               case ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+                       return "ECDHE_RSA_WITH_AES_128_GCM_SHA256";
+               case ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+                       return "ECDHE_RSA_WITH_AES_256_GCM_SHA384";
+               case ECDH_RSA_WITH_AES_128_GCM_SHA256:
+                       return "ECDH_RSA_WITH_AES_128_GCM_SHA256";
+               case ECDH_RSA_WITH_AES_256_GCM_SHA384:
+                       return "ECDH_RSA_WITH_AES_256_GCM_SHA384";
+               case ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+                       return "ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256";
+               case ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+                       return "ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256";
+               case DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+                       return "DHE_RSA_WITH_CHACHA20_POLY1305_SHA256";
+               case PSK_WITH_CHACHA20_POLY1305_SHA256:
+                       return "PSK_WITH_CHACHA20_POLY1305_SHA256";
+               case ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256:
+                       return "ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256";
+               case DHE_PSK_WITH_CHACHA20_POLY1305_SHA256:
+                       return "DHE_PSK_WITH_CHACHA20_POLY1305_SHA256";
+               case RSA_PSK_WITH_CHACHA20_POLY1305_SHA256:
+                       return "RSA_PSK_WITH_CHACHA20_POLY1305_SHA256";
+               case FALLBACK_SCSV:
+                       return "FALLBACK_SCSV";
+               case EMPTY_RENEGOTIATION_INFO_SCSV:
+                       return "EMPTY_RENEGOTIATION_INFO_SCSV";
+               default:
+                       return String.Format("UNKNOWN:0x{0:X4}", cipherSuite);
+               }
+       }
+
+       /*
+        * Get a human-readable name for a hash-and-sign algorithm.
+        */
+       public static string HashAndSignName(int hs)
+       {
+               switch (hs) {
+               case RSA_MD5:       return "RSA_MD5";
+               case RSA_SHA1:      return "RSA_SHA1";
+               case RSA_SHA224:    return "RSA_SHA224";
+               case RSA_SHA256:    return "RSA_SHA256";
+               case RSA_SHA384:    return "RSA_SHA384";
+               case RSA_SHA512:    return "RSA_SHA512";
+               case ECDSA_MD5:     return "ECDSA_MD5";
+               case ECDSA_SHA1:    return "ECDSA_SHA1";
+               case ECDSA_SHA224:  return "ECDSA_SHA224";
+               case ECDSA_SHA256:  return "ECDSA_SHA256";
+               case ECDSA_SHA384:  return "ECDSA_SHA384";
+               case ECDSA_SHA512:  return "ECDSA_SHA512";
+               default:
+                       return String.Format("UNKNOWN:0x{0:X4}", hs);
+               }
+       }
+
+       /*
+        * Get a human-readable name for a curve.
+        */
+       public static string CurveName(int id)
+       {
+               switch (id) {
+               case Curve25519:  return "Curve25519";
+               case NIST_P256:   return "NIST_P256";
+               case NIST_P384:   return "NIST_P384";
+               case NIST_P521:   return "NIST_P521";
+               default:
+                       return String.Format("UNKNOWN:0x{0:X4}", id);
+               }
+       }
+
+       /*
+        * Extract the public key from an encoded X.509 certificate.
+        * This does NOT make any attempt at validating the certificate.
+        */
+       internal static IPublicKey GetKeyFromCert(byte[] cert)
+       {
+               AsnElt ae = AsnElt.Decode(cert);
+               ae.CheckTag(AsnElt.SEQUENCE);
+               ae.CheckNumSub(3);
+               ae = ae.GetSub(0);
+               ae.CheckTag(AsnElt.SEQUENCE);
+               ae.CheckNumSubMin(6);
+               int off = 5;
+               if (ae.GetSub(0).TagValue != AsnElt.INTEGER) {
+                       ae.CheckNumSubMin(7);
+                       off ++;
+               }
+               return KF.DecodePublicKey(ae.GetSub(off));
+       }
+
+       internal static bool IsRSA(int cs)
+       {
+               switch (cs) {
+               case RSA_WITH_RC4_128_MD5:
+               case RSA_WITH_RC4_128_SHA:
+               case RSA_WITH_3DES_EDE_CBC_SHA:
+               case RSA_WITH_AES_128_CBC_SHA:
+               case RSA_WITH_AES_256_CBC_SHA:
+               case RSA_WITH_AES_128_CBC_SHA256:
+               case RSA_WITH_AES_256_CBC_SHA256:
+               case RSA_WITH_AES_128_GCM_SHA256:
+               case RSA_WITH_AES_256_GCM_SHA384:
+                       return true;
+               default:
+                       return false;
+               }
+       }
+
+       internal static bool IsDH_DSA(int cs)
+       {
+               switch (cs) {
+               case DH_DSS_WITH_3DES_EDE_CBC_SHA:
+               case DH_DSS_WITH_AES_128_CBC_SHA:
+               case DH_DSS_WITH_AES_256_CBC_SHA:
+               case DH_DSS_WITH_AES_128_CBC_SHA256:
+               case DH_DSS_WITH_AES_256_CBC_SHA256:
+               case DH_DSS_WITH_AES_128_GCM_SHA256:
+               case DH_DSS_WITH_AES_256_GCM_SHA384:
+                       return true;
+               default:
+                       return false;
+               }
+       }
+
+       internal static bool IsDH_RSA(int cs)
+       {
+               switch (cs) {
+               case DH_RSA_WITH_3DES_EDE_CBC_SHA:
+               case DH_RSA_WITH_AES_128_CBC_SHA:
+               case DH_RSA_WITH_AES_256_CBC_SHA:
+               case DH_RSA_WITH_AES_128_CBC_SHA256:
+               case DH_RSA_WITH_AES_256_CBC_SHA256:
+               case DH_RSA_WITH_AES_128_GCM_SHA256:
+               case DH_RSA_WITH_AES_256_GCM_SHA384:
+                       return true;
+               default:
+                       return false;
+               }
+       }
+
+       internal static bool IsDH(int cs)
+       {
+               return IsDH_DSA(cs) || IsDH_RSA(cs);
+       }
+
+       internal static bool IsDHE_DSS(int cs)
+       {
+               switch (cs) {
+               case DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+               case DHE_DSS_WITH_AES_128_CBC_SHA:
+               case DHE_DSS_WITH_AES_256_CBC_SHA:
+               case DHE_DSS_WITH_AES_128_CBC_SHA256:
+               case DHE_DSS_WITH_AES_256_CBC_SHA256:
+               case DHE_DSS_WITH_AES_128_GCM_SHA256:
+               case DHE_DSS_WITH_AES_256_GCM_SHA384:
+                       return true;
+               default:
+                       return false;
+               }
+       }
+
+       internal static bool IsDHE_RSA(int cs)
+       {
+               switch (cs) {
+               case DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+               case DHE_RSA_WITH_AES_128_CBC_SHA:
+               case DHE_RSA_WITH_AES_256_CBC_SHA:
+               case DHE_RSA_WITH_AES_128_CBC_SHA256:
+               case DHE_RSA_WITH_AES_256_CBC_SHA256:
+               case DHE_RSA_WITH_AES_128_GCM_SHA256:
+               case DHE_RSA_WITH_AES_256_GCM_SHA384:
+               case DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+                       return true;
+               default:
+                       return false;
+               }
+       }
+
+       internal static bool IsECDH_ECDSA(int cs)
+       {
+               switch (cs) {
+               case ECDH_ECDSA_WITH_NULL_SHA:
+               case ECDH_ECDSA_WITH_RC4_128_SHA:
+               case ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+               case ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+               case ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+               case ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+               case ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+               case ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+               case ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+                       return true;
+               default:
+                       return false;
+               }
+       }
+
+       internal static bool IsECDH_RSA(int cs)
+       {
+               switch (cs) {
+               case ECDH_RSA_WITH_NULL_SHA:
+               case ECDH_RSA_WITH_RC4_128_SHA:
+               case ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+               case ECDH_RSA_WITH_AES_128_CBC_SHA:
+               case ECDH_RSA_WITH_AES_256_CBC_SHA:
+               case ECDH_RSA_WITH_AES_128_CBC_SHA256:
+               case ECDH_RSA_WITH_AES_256_CBC_SHA384:
+               case ECDH_RSA_WITH_AES_128_GCM_SHA256:
+               case ECDH_RSA_WITH_AES_256_GCM_SHA384:
+                       return true;
+               default:
+                       return false;
+               }
+       }
+
+       internal static bool IsECDH(int cs)
+       {
+               return IsECDH_ECDSA(cs) || IsECDH_RSA(cs);
+       }
+
+       internal static bool IsECDHE_ECDSA(int cs)
+       {
+               switch (cs) {
+               case ECDHE_ECDSA_WITH_NULL_SHA:
+               case ECDHE_ECDSA_WITH_RC4_128_SHA:
+               case ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+               case ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+               case ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+               case ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+               case ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+               case ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+               case ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+               case ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+                       return true;
+               default:
+                       return false;
+               }
+       }
+
+       internal static bool IsECDHE_RSA(int cs)
+       {
+               switch (cs) {
+               case ECDHE_RSA_WITH_NULL_SHA:
+               case ECDHE_RSA_WITH_RC4_128_SHA:
+               case ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+               case ECDHE_RSA_WITH_AES_128_CBC_SHA:
+               case ECDHE_RSA_WITH_AES_256_CBC_SHA:
+               case ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+               case ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+               case ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+               case ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+               case ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+                       return true;
+               default:
+                       return false;
+               }
+       }
+
+       internal static bool IsECDHE(int cs)
+       {
+               return IsECDHE_RSA(cs) || IsECDHE_ECDSA(cs);
+       }
+
+       internal static bool IsSHA384(int cs)
+       {
+               switch (cs) {
+               case RSA_WITH_AES_256_GCM_SHA384:
+               case DH_DSS_WITH_AES_256_GCM_SHA384:
+               case DH_RSA_WITH_AES_256_GCM_SHA384:
+               case DHE_DSS_WITH_AES_256_GCM_SHA384:
+               case DHE_RSA_WITH_AES_256_GCM_SHA384:
+               case ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+               case ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+               case ECDH_RSA_WITH_AES_256_CBC_SHA384:
+               case ECDH_RSA_WITH_AES_256_GCM_SHA384:
+               case ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+               case ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+               case ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+               case ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+                       return true;
+               default:
+                       return false;
+               }
+       }
+
+       internal static bool IsTLS12(int cs)
+       {
+               switch (cs) {
+               case RSA_WITH_NULL_SHA256:
+               case RSA_WITH_AES_128_CBC_SHA256:
+               case RSA_WITH_AES_256_CBC_SHA256:
+               case DH_DSS_WITH_AES_128_CBC_SHA256:
+               case DH_RSA_WITH_AES_128_CBC_SHA256:
+               case DHE_DSS_WITH_AES_128_CBC_SHA256:
+               case DHE_RSA_WITH_AES_128_CBC_SHA256:
+               case DH_DSS_WITH_AES_256_CBC_SHA256:
+               case DH_RSA_WITH_AES_256_CBC_SHA256:
+               case DHE_DSS_WITH_AES_256_CBC_SHA256:
+               case DHE_RSA_WITH_AES_256_CBC_SHA256:
+               case DH_anon_WITH_AES_128_CBC_SHA256:
+               case DH_anon_WITH_AES_256_CBC_SHA256:
+               case RSA_WITH_AES_128_GCM_SHA256:
+               case RSA_WITH_AES_256_GCM_SHA384:
+               case DHE_RSA_WITH_AES_128_GCM_SHA256:
+               case DHE_RSA_WITH_AES_256_GCM_SHA384:
+               case DH_RSA_WITH_AES_128_GCM_SHA256:
+               case DH_RSA_WITH_AES_256_GCM_SHA384:
+               case DHE_DSS_WITH_AES_128_GCM_SHA256:
+               case DHE_DSS_WITH_AES_256_GCM_SHA384:
+               case DH_DSS_WITH_AES_128_GCM_SHA256:
+               case DH_DSS_WITH_AES_256_GCM_SHA384:
+               case DH_anon_WITH_AES_128_GCM_SHA256:
+               case DH_anon_WITH_AES_256_GCM_SHA384:
+               case ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+               case ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+               case ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+               case ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+               case ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+               case ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+               case ECDH_RSA_WITH_AES_128_CBC_SHA256:
+               case ECDH_RSA_WITH_AES_256_CBC_SHA384:
+               case ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+               case ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+               case ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+               case ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+               case ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+               case ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+               case ECDH_RSA_WITH_AES_128_GCM_SHA256:
+               case ECDH_RSA_WITH_AES_256_GCM_SHA384:
+               case ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+               case ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+               case DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+               case PSK_WITH_CHACHA20_POLY1305_SHA256:
+               case ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256:
+               case DHE_PSK_WITH_CHACHA20_POLY1305_SHA256:
+               case RSA_PSK_WITH_CHACHA20_POLY1305_SHA256:
+                       return true;
+               default:
+                       return false;
+               }
+       }
+
+       internal static PRF GetPRFForTLS12(int cs)
+       {
+               return new PRF(IsSHA384(cs)
+                       ? (IDigest)new SHA384()
+                       : (IDigest)new SHA256());
+       }
+
+       internal static ECCurve GetCurveByID(int id)
+       {
+               switch (id) {
+               case NIST_P256: return EC.P256;
+               case NIST_P384: return EC.P384;
+               case NIST_P521: return EC.P521;
+               case Curve25519: return EC.Curve25519;
+               default:
+                       throw new SSLException("Unknown curve: " + id);
+               }
+       }
+
+       /*
+        * Get ID for a curve. This returns -1 if the curve is not
+        * recognised.
+        */
+       internal static int CurveToID(ECCurve curve)
+       {
+               switch (curve.Name) {
+               case "P-256":       return SSL.NIST_P256;
+               case "P-384":       return SSL.NIST_P384;
+               case "P-521":       return SSL.NIST_P521;
+               case "Curve25519":  return SSL.Curve25519;
+               default:
+                       return -1;
+               }
+       }
+
+       internal static IDigest GetHashByID(int id)
+       {
+               switch (id) {
+               case 1: return new MD5();
+               case 2: return new SHA1();
+               case 3: return new SHA224();
+               case 4: return new SHA256();
+               case 5: return new SHA384();
+               case 6: return new SHA512();
+               default:
+                       throw new SSLException("Unknown hash: " + id);
+               }
+       }
+}
+
+}
diff --git a/SSLTLS/SSLClient.cs b/SSLTLS/SSLClient.cs
new file mode 100644 (file)
index 0000000..2122b94
--- /dev/null
@@ -0,0 +1,905 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+/*
+ * Class for a SSL client connection.
+ *
+ * An instance is created over a specified transport stream. SSL session
+ * parameters from a previous connection are optionally specified, to
+ * attempt session resumption. The instance handles the connection but
+ * cannot be "revived" after the connection was closed (the session
+ * parameters, though, can be extracted and used with another instance).
+ */
+
+public class SSLClient : SSLEngine {
+
+       /*
+        * Create the client over the provided stream. No attempt at
+        * session resumption will be made.
+        */
+       public SSLClient(Stream sub) : this(sub, null)
+       {
+       }
+
+       /*
+        * Create the client over the provided stream. If the
+        * 'sessionParameters' are not null, then the client will try to
+        * resume that session. Note that session parameters may include
+        * a "target server name", in which case the ServerName
+        * property will be set to that name, which will be included
+        * in the ClientHello as the Server Name Indication extension.
+        */
+       public SSLClient(Stream sub, SSLSessionParameters sessionParameters)
+               : base(sub)
+       {
+               sentExtensions = new List<int>();
+               resumeParams = sessionParameters;
+               if (resumeParams != null) {
+                       string name = resumeParams.ServerName;
+                       if (name != null) {
+                               ServerName = name;
+                       }
+               }
+       }
+
+       /*
+        * Validator for the server certificate. This callback is
+        * responsible for obtaining the server's public key and making
+        * sure it is the right one. A normal, standard-compliant
+        * implementation should do the following:
+        *
+        * -- Validate the certificate as X.509 mandates (building
+        * a path to a trust anchor, and verifying all signatures, names
+        * and appropriate certificate extensions; also obtaining
+        * proper CRL or OCSP response for a fresh revocation status).
+        *
+        * -- Check that the intended server name (provided in the
+        * 'serverName' parameter) matches that which is found in the
+        * certificate (see RFC 2818 section 3.1 for details; also
+        * consider RFC 6125 section 6.4).
+        *
+        * -- Return the public key found in the server's certificate,
+        * along with its allowed usages (which may depend on the
+        * KeyUsage extensions found in the certificate).
+        *
+        * The certificate chain, as received from the server, is
+        * provided as parameter; it is non-empty (it contains at least
+        * one certificate). The server's certificate is the first one
+        * in the chain.
+        *
+        * The 'serverName' parameter is the intended server name, to
+        * match against the names found in the certificate. If it is
+        * null, then no matching is expected (this correspond to the
+        * ServerName property in this SSLClient instance).
+        *
+        * The 'usage' variable shall be set to a value that qualifies
+        * whether the key may be used for encryption and/or signatures.
+        */
+       public delegate IPublicKey CertValidator(
+               byte[][] chain, string serverName, out KeyUsage usage);
+       public CertValidator ServerCertValidator {
+               get; set;
+       }
+
+       /*
+        * A simple INSECURE certificate "validator" that does not validate
+        * anything: the public key is extracted and returned, with no
+        * other checks. THIS IS FOR TESTS ONLY. Using this validator
+        * basically voids all security properties of SSL.
+        */
+       public static IPublicKey InsecureCertValidator(
+               byte[][] chain, string serverName, out KeyUsage usage)
+       {
+               usage = KeyUsage.EncryptAndSign;
+               return SSL.GetKeyFromCert(chain[0]);
+       }
+
+       List<int> sentExtensions;
+       SSLSessionParameters resumeParams;
+
+       internal override bool IsClient {
+               get {
+                       return true;
+               }
+       }
+
+       internal override bool DoHandshake()
+       {
+               CheckConfigHashAndSign();
+
+               ResetHashes();
+               MakeRandom(clientRandom);
+
+               SendClientHello();
+               FlushSub();
+
+               bool resume;
+               if (!ParseServerHello(out resume)) {
+                       return false;
+               }
+               HandshakeCount ++;
+               if (resume) {
+                       /*
+                        * Abbreviated handshake.
+                        */
+                       ParseCCSAndFinished();
+                       SendCCSAndFinished();
+                       FlushSub();
+                       SetAppData();
+                       IsResume = true;
+                       return true;
+               }
+
+               KeyUsage usage;
+               IPublicKey pkey = ParseCertificate(out usage);
+
+               ECCurve curve;
+               byte[] serverPoint;
+               if (SSL.IsECDHE_RSA(CipherSuite)) {
+                       if (!(pkey is RSAPublicKey)) {
+                               throw new SSLException(
+                                       "ECDHE_RSA needs a RSA public key");
+                       }
+                       if (usage != KeyUsage.SignOnly
+                               && usage != KeyUsage.EncryptAndSign)
+                       {
+                               throw new SSLException("Server public key"
+                                       + " unfit for signatures");
+                       }
+                       serverPoint = ParseServerKeyExchange(out curve, pkey);
+               } else if (SSL.IsECDHE_ECDSA(CipherSuite)) {
+                       if (!(pkey is ECPublicKey)) {
+                               throw new SSLException(
+                                       "ECDHE_ECDSA needs an EC public key");
+                       }
+                       if (usage != KeyUsage.SignOnly
+                               && usage != KeyUsage.EncryptAndSign)
+                       {
+                               throw new SSLException("Server public key"
+                                       + " unfit for signatures");
+                       }
+                       serverPoint = ParseServerKeyExchange(out curve, pkey);
+               } else {
+                       curve = null;
+                       serverPoint = null;
+               }
+
+               bool reqClientCert = false;
+               int mt;
+               byte[] msg = ReadHandshakeMessage(out mt);
+               if (mt == SSL.CERTIFICATE_REQUEST) {
+                       /*
+                        * FIXME: parse message and select a client
+                        * certificate.
+                        */
+                       reqClientCert = true;
+                       msg = ReadHandshakeMessage(out mt);
+               }
+               if (mt != SSL.SERVER_HELLO_DONE) {
+                       throw new SSLException(string.Format("Unexpected"
+                               + " handshake message {0}"
+                               + " (expected: ServerHelloDone)", mt));
+               }
+               if (msg.Length != 0) {
+                       throw new SSLException(
+                               "Invalid ServerHelloDone (not empty)");
+               }
+
+               if (reqClientCert) {
+                       /*
+                        * FIXME: right now, we send an empty Certificate
+                        * message if the server asks for a client
+                        * certificate; i.e., we claim we have none.
+                        */
+                       MemoryStream ms =
+                               StartHandshakeMessage(SSL.CERTIFICATE);
+                       EndHandshakeMessage(ms);
+               }
+
+               if (SSL.IsRSA(CipherSuite)) {
+                       if (!(pkey is RSAPublicKey)) {
+                               throw new SSLException(
+                                       "Server public key is not RSA");
+                       }
+                       if (usage != KeyUsage.EncryptOnly
+                               && usage != KeyUsage.EncryptAndSign)
+                       {
+                               throw new SSLException("Server public key is"
+                                       + " not allowed for encryption");
+                       }
+                       SendClientKeyExchangeRSA(pkey as RSAPublicKey);
+               } else if (SSL.IsECDH(CipherSuite)) {
+                       if (!(pkey is ECPublicKey)) {
+                               throw new SSLException(
+                                       "Server public key is not EC");
+                       }
+                       if (usage != KeyUsage.EncryptOnly
+                               && usage != KeyUsage.EncryptAndSign)
+                       {
+                               throw new SSLException("Server public key is"
+                                       + " not allowed for key exchange");
+                       }
+                       SendClientKeyExchangeECDH(pkey as ECPublicKey);
+               } else if (serverPoint != null) {
+                       SendClientKeyExchangeECDH(curve, serverPoint);
+               } else {
+                       /*
+                        * TODO: Maybe support DHE cipher suites?
+                        */
+                       throw new Exception("NYI");
+               }
+
+               /*
+                * FIXME: when client certificates are supported, we
+                * will need to send a CertificateVerify message here.
+                */
+
+               SendCCSAndFinished();
+               FlushSub();
+
+               ParseCCSAndFinished();
+               SetAppData();
+               IsResume = false;
+
+               return true;
+       }
+
+       void SendClientHello()
+       {
+               MemoryStream ms = StartHandshakeMessage(SSL.CLIENT_HELLO);
+
+               // Maximum supported protocol version.
+               IO.Write16(ms, VersionMax);
+
+               // Client random.
+               ms.Write(clientRandom, 0, clientRandom.Length);
+
+               // Session ID.
+               if (resumeParams != null) {
+                       byte[] id = resumeParams.SessionID;
+                       ms.WriteByte((byte)id.Length);
+                       ms.Write(id, 0, id.Length);
+               } else {
+                       ms.WriteByte(0x00);
+               }
+
+               // List of supported cipher suites.
+               int csLen = SupportedCipherSuites.Length << 1;
+               int extraCS = GetQuirkInt("sendExtraCipherSuite", -1);
+               if (extraCS >= 0) {
+                       csLen += 2;
+               }
+               IO.Write16(ms, csLen);
+               foreach (int cs in SupportedCipherSuites) {
+                       IO.Write16(ms, cs);
+               }
+               if (extraCS >= 0) {
+                       IO.Write16(ms, extraCS);
+               }
+
+               // List of supported compression algorithms.
+               ms.WriteByte(0x01);
+               ms.WriteByte(0x00);
+
+               // Extensions
+               sentExtensions.Clear();
+               MemoryStream chExt = new MemoryStream();
+
+               // Server Name Indication
+               if (ServerName != null && ServerName.Length > 0) {
+                       byte[] encName = Encoding.UTF8.GetBytes(ServerName);
+                       int elen = encName.Length;
+                       if (elen > 65530) {
+                               throw new SSLException("Oversized server name");
+                       }
+                       sentExtensions.Add(0x0000);
+                       IO.Write16(chExt, 0x0000);    // extension type
+                       IO.Write16(chExt, elen + 5);  // extension length
+                       IO.Write16(chExt, elen + 3);  // name list length
+                       chExt.WriteByte(0x00);        // name type
+                       IO.Write16(chExt, elen);      // name length
+                       chExt.Write(encName, 0, elen);
+               }
+
+               // Supported Curves and Supported Point Formats
+               if (SupportedCurves != null && SupportedCurves.Length > 0) {
+                       int len = SupportedCurves.Length;
+                       sentExtensions.Add(0x000A);
+                       IO.Write16(chExt, 0x000A);
+                       IO.Write16(chExt, (len << 1) + 2);
+                       IO.Write16(chExt, len << 1);
+                       foreach (int cc in SupportedCurves) {
+                               IO.Write16(chExt, cc);
+                       }
+
+                       sentExtensions.Add(0x000B);
+                       IO.Write16(chExt, 0x000B);
+                       IO.Write16(chExt, 2);
+                       chExt.WriteByte(1);
+                       chExt.WriteByte(0x00);
+               }
+
+               // Supported Signatures
+               if (VersionMax >= SSL.TLS12 && SupportedHashAndSign != null
+                       && SupportedHashAndSign.Length > 0)
+               {
+                       sentExtensions.Add(0x000D);
+                       IO.Write16(chExt, 0x000D);
+                       int num = SupportedHashAndSign.Length;
+                       IO.Write16(chExt, 2 + (num << 1));
+                       IO.Write16(chExt, num << 1);
+                       foreach (int hs in SupportedHashAndSign) {
+                               IO.Write16(chExt, hs);
+                       }
+               }
+
+               // Secure renegotiation
+               if (!GetQuirkBool("noSecureReneg")) {
+                       sentExtensions.Add(0xFF01);
+                       IO.Write16(chExt, 0xFF01);
+                       byte[] exv;
+                       if (renegSupport > 0) {
+                               exv = savedClientFinished;
+                       } else {
+                               exv = new byte[0];
+                       }
+
+                       if (GetQuirkBool("forceEmptySecureReneg")) {
+                               exv = new byte[0];
+                       } else if (GetQuirkBool("forceNonEmptySecureReneg")) {
+                               exv = new byte[12];
+                       } else if (GetQuirkBool("alterNonEmptySecureReneg")) {
+                               if (exv.Length > 0) {
+                                       exv[exv.Length - 1] ^= 0x01;
+                               }
+                       } else if (GetQuirkBool("oversizedSecureReneg")) {
+                               exv = new byte[255];
+                       }
+
+                       IO.Write16(chExt, exv.Length + 1);
+                       chExt.WriteByte((byte)exv.Length);
+                       chExt.Write(exv, 0, exv.Length);
+               }
+
+               // Extra extension with random contents.
+               int extraExt = GetQuirkInt("sendExtraExtension", -1);
+               if (extraExt >= 0) {
+                       byte[] exv = new byte[extraExt >> 16];
+                       RNG.GetBytes(exv);
+                       IO.Write16(chExt, extraExt & 0xFFFF);
+                       IO.Write16(chExt, exv.Length);
+                       chExt.Write(exv, 0, exv.Length);
+               }
+
+               // Max Fragment Length
+               // ALPN
+               // FIXME
+
+               byte[] encExt = chExt.ToArray();
+               if (encExt.Length > 0) {
+                       if (encExt.Length > 65535) {
+                               throw new SSLException("Oversized extensions");
+                       }
+                       IO.Write16(ms, encExt.Length);
+                       ms.Write(encExt, 0, encExt.Length);
+               }
+
+               EndHandshakeMessage(ms);
+       }
+
+       bool ParseServerHello(out bool resume)
+       {
+               resume = false;
+
+               int mt;
+               byte[] msg = ReadHandshakeMessage(out mt, FirstHandshakeDone);
+               if (msg == null) {
+                       /*
+                        * Server denies attempt explicitly.
+                        */
+                       return false;
+               }
+               if (mt != SSL.SERVER_HELLO) {
+                       throw new SSLException(string.Format("Unexpected"
+                               + " handshake message {0} (expecting a"
+                               + " ServerHello)", mt));
+               }
+
+               if (msg.Length < 38) {
+                       throw new SSLException("Truncated ServerHello");
+               }
+               Version = IO.Dec16be(msg, 0);
+               if (Version < VersionMin || Version > VersionMax) {
+                       throw new SSLException(string.Format(
+                               "Unsupported version: 0x{0:X4}", Version));
+               }
+               Array.Copy(msg, 2, serverRandom, 0, 32);
+               int idLen = msg[34];
+               if (idLen > 32) {
+                       throw new SSLException("Invalid session ID length");
+               }
+               if (idLen + 38 > msg.Length) {
+                       throw new SSLException("Truncated ServerHello");
+               }
+               sessionID = new byte[idLen];
+               Array.Copy(msg, 35, sessionID, 0, idLen);
+               int off = 35 + idLen;
+
+               /*
+                * Cipher suite. It must be one of the suites we sent.
+                */
+               CipherSuite = IO.Dec16be(msg, off);
+               off += 2;
+               bool found = false;
+               foreach (int cs in SupportedCipherSuites) {
+                       if (cs == SSL.FALLBACK_SCSV
+                               || cs == SSL.EMPTY_RENEGOTIATION_INFO_SCSV)
+                       {
+                               continue;
+                       }
+                       if (cs == CipherSuite) {
+                               found = true;
+                               break;
+                       }
+               }
+               if (!found) {
+                       throw new SSLException(string.Format(
+                               "Server selected cipher suite 0x{0:X4}"
+                               + " which we did not advertise", CipherSuite));
+               }
+
+               /*
+                * Compression. Must be 0, since we do not support it.
+                */
+               int comp = msg[off ++];
+               if (comp != 0x00) {
+                       throw new SSLException(string.Format(
+                               "Server selected compression {0}"
+                               + " which we did not advertise", comp));
+               }
+
+               /*
+                * Extensions. Each extension from the server should
+                * correspond to an extension sent in the ClientHello.
+                */
+               bool secReneg = false;
+               if (msg.Length > off) {
+                       if (msg.Length == off + 1) {
+                               throw new SSLException("Truncated ServerHello");
+                       }
+                       int tlen = IO.Dec16be(msg, off);
+                       off += 2;
+                       if (tlen != msg.Length - off) {
+                               throw new SSLException(
+                                       "Invalid extension list length");
+                       }
+                       while (off < msg.Length) {
+                               if ((off + 4) > msg.Length) {
+                                       throw new SSLException(
+                                               "Truncated extention");
+                               }
+                               int etype = IO.Dec16be(msg, off);
+                               int elen = IO.Dec16be(msg, off + 2);
+                               off += 4;
+                               if (elen > msg.Length - off) {
+                                       throw new SSLException(
+                                               "Truncated extention");
+                               }
+                               if (!sentExtensions.Contains(etype)) {
+                                       throw new SSLException(string.Format(
+                                               "Server send unadvertised"
+                                               + " extenstion 0x{0:X4}",
+                                               etype));
+                               }
+
+                               /*
+                                * We have some processing to do on some
+                                * specific server-side extensions.
+                                */
+                               switch (etype) {
+
+                               case 0xFF01:
+                                       secReneg = true;
+                                       ParseExtSecureReneg(msg, off, elen);
+                                       break;
+                               }
+
+                               off += elen;
+                       }
+               }
+
+               /*
+                * Renegotiation support: if we did not get the extension
+                * from the server, then secure renegotiation is
+                * definitely not supported. If it _was_ known as
+                * being supported (from a previous handshake) then this
+                * is a fatal error.
+                */
+               if (!secReneg) {
+                       if (renegSupport > 0) {
+                               throw new SSLException("Missing Secure"
+                                       + " Renegotiation extension");
+                       }
+                       renegSupport = -1;
+               }
+
+               /*
+                * Check whether this is a session resumption: a session
+                * is resumed if we sent a non-empty session ID, and the
+                * ServerHello contained the same session ID.
+                *
+                * In case of resumption, the ServerHello must use the
+                * same version and cipher suite than in the saved
+                * parameters.
+                */
+               if (resumeParams != null) {
+                       byte[] id = resumeParams.SessionID;
+                       if (id.Length > 0 && IO.Eq(id, sessionID)) {
+                               if (Version != resumeParams.Version) {
+                                       throw new SSLException(
+                                               "Resume version mismatch");
+                               }
+                               if (CipherSuite != resumeParams.CipherSuite) {
+                                       throw new SSLException(
+                                               "Resume cipher suite mismatch");
+                               }
+                               SetMasterSecret(resumeParams.MasterSecret);
+                               resume = true;
+                       }
+               }
+
+               return true;
+       }
+
+       void ParseExtSecureReneg(byte[] buf, int off, int len)
+       {
+               if (len < 1 || len != 1 + buf[off]) {
+                       throw new SSLException(
+                               "Invalid Secure Renegotiation extension");
+               }
+               len --;
+               off ++;
+
+               if (renegSupport == 0) {
+                       /*
+                        * Initial handshake: extension MUST be empty.
+                        */
+                       if (len != 0) {
+                               throw new SSLException(
+                                       "Non-empty Secure Renegotation"
+                                       + " on initial handshake");
+                       }
+                       renegSupport = 1;
+               } else {
+                       /*
+                        * Renegotiation: extension MUST contain the
+                        * concatenation of the saved client and
+                        * server Finished messages (in that order).
+                        */
+                       if (len != 24) {
+                               throw new SSLException(
+                                       "Wrong Secure Renegotiation value");
+                       }
+                       int z = 0;
+                       for (int i = 0; i < 12; i ++) {
+                               z |= savedClientFinished[i] ^ buf[off + i];
+                               z |= savedServerFinished[i] ^ buf[off + 12 + i];
+                       }
+                       if (z != 0) {
+                               throw new SSLException(
+                                       "Wrong Secure Renegotiation value");
+                       }
+               }
+       }
+
+       IPublicKey ParseCertificate(out KeyUsage usage)
+       {
+               byte[] msg = ReadHandshakeMessageExpected(SSL.CERTIFICATE);
+               if (msg.Length < 3) {
+                       throw new SSLException("Invalid Certificate message");
+               }
+               int tlen = IO.Dec24be(msg, 0);
+               int off = 3;
+               if (tlen != msg.Length - off) {
+                       throw new SSLException("Invalid Certificate message");
+               }
+               List<byte[]> certs = new List<byte[]>();
+               while (off < msg.Length) {
+                       if (msg.Length - off < 3) {
+                               throw new SSLException(
+                                       "Invalid Certificate message");
+                       }
+                       int clen = IO.Dec24be(msg, off);
+                       off += 3;
+                       if (clen > msg.Length - off) {
+                               throw new SSLException(
+                                       "Invalid Certificate message");
+                       }
+                       byte[] ec = new byte[clen];
+                       Array.Copy(msg, off, ec, 0, clen);
+                       off += clen;
+                       certs.Add(ec);
+               }
+
+               return ServerCertValidator(
+                       certs.ToArray(), ServerName, out usage);
+       }
+
+       byte[] ParseServerKeyExchange(out ECCurve curve, IPublicKey pkey)
+       {
+               byte[] msg = ReadHandshakeMessageExpected(
+                       SSL.SERVER_KEY_EXCHANGE);
+               if (msg.Length < 4) {
+                       throw new SSLException(
+                               "Invalid ServerKeyExchange message");
+               }
+               if (msg[0] != 0x03) {
+                       throw new SSLException("Unsupported unnamed curve");
+               }
+               curve = SSL.GetCurveByID(IO.Dec16be(msg, 1));
+               int plen = msg[3];
+               int off = 4;
+               if (msg.Length - off < plen) {
+                       throw new SSLException(
+                               "Invalid ServerKeyExchange message");
+               }
+               byte[] point = new byte[plen];
+               Array.Copy(msg, off, point, 0, plen);
+               off += plen;
+               int slen = off;
+
+               int hashId, sigId;
+               if (Version >= SSL.TLS12) {
+                       if (msg.Length - off < 2) {
+                               throw new SSLException(
+                                       "Invalid ServerKeyExchange message");
+                       }
+                       hashId = msg[off ++];
+                       if (hashId == 0) {
+                               throw new SSLException(
+                                       "Invalid hash identifier");
+                       }
+                       sigId = msg[off ++];
+               } else {
+                       if (pkey is RSAPublicKey) {
+                               hashId = 0;
+                               sigId = 1;
+                       } else if (pkey is ECPublicKey) {
+                               hashId = 2;
+                               sigId = 3;
+                       } else {
+                               throw new SSLException(
+                                       "Unsupported signature key type");
+                       }
+               }
+               
+               if (msg.Length - off < 2) {
+                       throw new SSLException(
+                               "Invalid ServerKeyExchange message");
+               }
+               int sigLen = IO.Dec16be(msg, off);
+               off += 2;
+               if (sigLen != msg.Length - off) {
+                       throw new SSLException(
+                               "Invalid ServerKeyExchange message");
+               }
+               byte[] sig = new byte[sigLen];
+               Array.Copy(msg, off, sig, 0, sigLen);
+
+               byte[] hv;
+               if (hashId == 0) {
+                       MD5 md5 = new MD5();
+                       SHA1 sha1 = new SHA1();
+                       md5.Update(clientRandom);
+                       md5.Update(serverRandom);
+                       md5.Update(msg, 0, slen);
+                       sha1.Update(clientRandom);
+                       sha1.Update(serverRandom);
+                       sha1.Update(msg, 0, slen);
+                       hv = new byte[36];
+                       md5.DoFinal(hv, 0);
+                       sha1.DoFinal(hv, 16);
+               } else {
+                       IDigest h = SSL.GetHashByID(hashId);
+                       h.Update(clientRandom);
+                       h.Update(serverRandom);
+                       h.Update(msg, 0, slen);
+                       hv = h.DoFinal();
+               }
+
+               bool ok;
+               if (sigId == 1) {
+                       RSAPublicKey rpk = pkey as RSAPublicKey;
+                       if (rpk == null) {
+                               throw new SSLException(
+                                       "Wrong public key type for RSA");
+                       }
+                       if (hashId == 0) {
+                               ok = RSA.VerifyND(rpk, hv, sig);
+                       } else {
+                               byte[] head1, head2;
+
+                               switch (hashId) {
+                               case 1:
+                                       head1 = RSA.PKCS1_MD5;
+                                       head2 = RSA.PKCS1_MD5_ALT;
+                                       break;
+                               case 2:
+                                       head1 = RSA.PKCS1_SHA1;
+                                       head2 = RSA.PKCS1_SHA1_ALT;
+                                       break;
+                               case 3:
+                                       head1 = RSA.PKCS1_SHA224;
+                                       head2 = RSA.PKCS1_SHA224_ALT;
+                                       break;
+                               case 4:
+                                       head1 = RSA.PKCS1_SHA256;
+                                       head2 = RSA.PKCS1_SHA256_ALT;
+                                       break;
+                               case 5:
+                                       head1 = RSA.PKCS1_SHA384;
+                                       head2 = RSA.PKCS1_SHA384_ALT;
+                                       break;
+                               case 6:
+                                       head1 = RSA.PKCS1_SHA512;
+                                       head2 = RSA.PKCS1_SHA512_ALT;
+                                       break;
+                               default:
+                                       throw new SSLException(
+                                               "Unsupported hash algorithm: "
+                                               + hashId);
+                               }
+                               ok = RSA.Verify(rpk, head1, head2, hv, sig);
+                       }
+               } else if (sigId == 3) {
+                       ECPublicKey epk = pkey as ECPublicKey;
+                       if (epk == null) {
+                               throw new SSLException(
+                                       "Wrong public key type for ECDSA");
+                       }
+                       ok = ECDSA.Verify(epk, hv, sig);
+               } else {
+                       throw new SSLException(
+                               "Unsupported signature type: " + sigId);
+               }
+
+               if (!ok) {
+                       throw new SSLException(
+                               "Invalid signature on ServerKeyExchange");
+               }
+               return point;
+       }
+
+       void SendClientKeyExchangeRSA(RSAPublicKey pkey)
+       {
+               byte[] pms = new byte[48];
+               IO.Enc16be(Version, pms, 0);
+               RNG.GetBytes(pms, 2, pms.Length - 2);
+               byte[] epms = RSA.Encrypt(pkey, pms);
+               MemoryStream ms =
+                       StartHandshakeMessage(SSL.CLIENT_KEY_EXCHANGE);
+               IO.Write16(ms, epms.Length);
+               ms.Write(epms, 0, epms.Length);
+               EndHandshakeMessage(ms);
+               ComputeMaster(pms);
+       }
+
+       void SendClientKeyExchangeECDH(ECPublicKey pkey)
+       {
+               ECCurve curve = pkey.Curve;
+               SendClientKeyExchangeECDH(curve, pkey.Pub);
+       }
+
+       void SendClientKeyExchangeECDH(ECCurve curve, byte[] pub)
+       {
+               byte[] k = curve.MakeRandomSecret();
+
+               /*
+                * Compute the point P = k*G that we will send.
+                */
+               byte[] P = curve.GetGenerator(false);
+               uint good = curve.Mul(P, k, P, false);
+
+               /*
+                * Compute the shared secret Q = k*Pub.
+                */
+               byte[] Q = new byte[P.Length];
+               good &= curve.Mul(pub, k, Q, false);
+
+               if (good == 0) {
+                       /*
+                        * This might happen only if the server's public
+                        * key is not part of the proper subgroup. This
+                        * cannot happen with NIST's "P" curves.
+                        */
+                       throw new SSLException("ECDH failed");
+               }
+
+               /*
+                * Send message.
+                */
+               MemoryStream ms =
+                       StartHandshakeMessage(SSL.CLIENT_KEY_EXCHANGE);
+               ms.WriteByte((byte)P.Length);
+               ms.Write(P, 0, P.Length);
+               EndHandshakeMessage(ms);
+
+               /*
+                * Extract premaster secret.
+                */
+               int xlen;
+               int xoff = curve.GetXoff(out xlen);
+               byte[] pms = new byte[xlen];
+               Array.Copy(Q, xoff, pms, 0, xlen);
+               ComputeMaster(pms);
+       }
+
+       internal override void ProcessExtraHandshake()
+       {
+               /*
+                * If we receive a non-empty handshake message, then
+                * it should be an HelloRequest. Note that we expect
+                * the request not to be mixed with application data
+                * records (though it could be split).
+                */
+               ReadHelloRequests();
+
+               /*
+                * We accept to renegotiate only if the server supports
+                * the Secure Renegotiation extension.
+                */
+               if (renegSupport != 1) {
+                       SendWarning(SSL.NO_RENEGOTIATION);
+                       SetAppData();
+                       return;
+               }
+
+               /*
+                * We do a new handshake. We explicitly refuse to reuse
+                * session parameters, because there is no point in
+                * renegotiation if this resumes the same session.
+                */
+               resumeParams = null;
+               DoHandshake();
+       }
+
+       internal override void PrepareRenegotiate()
+       {
+               /*
+                * Nothing to do to trigger a new handshake, the client
+                * just has to send the ClientHello right away.
+                */
+       }
+}
+
+}
diff --git a/SSLTLS/SSLEngine.cs b/SSLTLS/SSLEngine.cs
new file mode 100644 (file)
index 0000000..e4ebf95
--- /dev/null
@@ -0,0 +1,1688 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+/*
+ * This is the base class common to SSLClient and SSLServer.
+ */
+
+public abstract class SSLEngine : Stream {
+
+       Stream sub;
+       InputRecord inRec;
+       OutputRecord outRec;
+       int deferredAlert;
+       int state;
+       int versionMin;
+       int actualVersion;
+       bool receivedNoReneg;
+
+       /*
+        * Functions used to hash handshake messages.
+        */
+       MD5 md5;
+       SHA1 sha1;
+       SHA256 sha256;
+       SHA384 sha384;
+
+       /*
+        * State:
+        *   STATE_HANDSHAKE   expecting handshake message only
+        *   STATE_CCS         expecting Change Cipher Spec message only
+        *   STATE_APPDATA     expecting application data or handshake
+        *   STATE_CLOSING     expecting only alert (close_notify)
+        *   STATE_CLOSED      closed
+        */
+       internal const int STATE_CLOSED    = 0;
+       internal const int STATE_HANDSHAKE = 1;
+       internal const int STATE_CCS       = 2;
+       internal const int STATE_APPDATA   = 3;
+       internal const int STATE_CLOSING   = 4;
+
+       /*
+        * Default cipher suites.
+        */
+       static int[] DEFAULT_CIPHER_SUITES = {
+               SSL.ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
+               SSL.ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+
+               SSL.ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+               SSL.ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+               SSL.ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+               SSL.ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+               SSL.ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
+               SSL.ECDHE_RSA_WITH_AES_128_CBC_SHA256,
+               SSL.ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
+               SSL.ECDHE_RSA_WITH_AES_256_CBC_SHA384,
+               SSL.ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+               SSL.ECDHE_RSA_WITH_AES_128_CBC_SHA,
+               SSL.ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+               SSL.ECDHE_RSA_WITH_AES_256_CBC_SHA,
+
+               SSL.ECDH_ECDSA_WITH_AES_128_GCM_SHA256,
+               SSL.ECDH_RSA_WITH_AES_128_GCM_SHA256,
+               SSL.ECDH_ECDSA_WITH_AES_256_GCM_SHA384,
+               SSL.ECDH_RSA_WITH_AES_256_GCM_SHA384,
+               SSL.ECDH_ECDSA_WITH_AES_128_CBC_SHA256,
+               SSL.ECDH_RSA_WITH_AES_128_CBC_SHA256,
+               SSL.ECDH_ECDSA_WITH_AES_256_CBC_SHA384,
+               SSL.ECDH_RSA_WITH_AES_256_CBC_SHA384,
+               SSL.ECDH_ECDSA_WITH_AES_128_CBC_SHA,
+               SSL.ECDH_RSA_WITH_AES_128_CBC_SHA,
+               SSL.ECDH_ECDSA_WITH_AES_256_CBC_SHA,
+               SSL.ECDH_RSA_WITH_AES_256_CBC_SHA,
+
+               SSL.RSA_WITH_AES_128_GCM_SHA256,
+               SSL.RSA_WITH_AES_256_GCM_SHA384,
+               SSL.RSA_WITH_AES_128_CBC_SHA256,
+               SSL.RSA_WITH_AES_256_CBC_SHA256,
+               SSL.RSA_WITH_AES_128_CBC_SHA,
+               SSL.RSA_WITH_AES_256_CBC_SHA,
+
+               SSL.ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
+               SSL.ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
+               SSL.ECDH_RSA_WITH_3DES_EDE_CBC_SHA,
+               SSL.ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
+               SSL.RSA_WITH_3DES_EDE_CBC_SHA
+       };
+
+       /*
+        * Default curves.
+        */
+       static int[] DEFAULT_CURVES = {
+               SSL.Curve25519,
+               SSL.NIST_P256,
+               SSL.NIST_P384,
+               SSL.NIST_P521
+       };
+
+       /*
+        * Default hash and sign algorithms.
+        */
+       static int[] DEFAULT_HASHANDSIGN = {
+               SSL.ECDSA_SHA256,
+               SSL.RSA_SHA256,
+               SSL.ECDSA_SHA224,
+               SSL.RSA_SHA224,
+               SSL.ECDSA_SHA384,
+               SSL.RSA_SHA384,
+               SSL.ECDSA_SHA512,
+               SSL.RSA_SHA512,
+               SSL.ECDSA_SHA1,
+               SSL.RSA_SHA1
+       };
+
+       internal byte[] clientRandom;
+       internal byte[] serverRandom;
+       internal byte[] sessionID;
+
+       /*
+        * 'renegSupport' is one of:
+        *    0   initial handshake not done yet
+        *    1   peer supports secure renegotiation
+        *   -1   peer does not support secure renegotiation
+        */
+       internal int renegSupport;
+       internal byte[] savedClientFinished;
+       internal byte[] savedServerFinished;
+
+       byte[] masterSecret;
+
+       /*
+        * Create a new engine over the provided transport stream.
+        */
+       public SSLEngine(Stream sub)
+       {
+               this.sub = sub;
+               inRec = new InputRecord(sub);
+               outRec = new OutputRecord(sub);
+               md5 = new MD5();
+               sha1 = new SHA1();
+               sha256 = new SHA256();
+               sha384 = new SHA384();
+               state = STATE_HANDSHAKE;
+               deferredAlert = -1;
+               AutoFlush = true;
+               CloseSub = true;
+               OnClose = null;
+               NoCloseNotify = false;
+               ClosedWithoutNotify = false;
+               MaximumHandshakeMessageLength = 65536;
+               SupportedCipherSuites = DEFAULT_CIPHER_SUITES;
+               SupportedCurves = DEFAULT_CURVES;
+               SupportedHashAndSign = DEFAULT_HASHANDSIGN;
+               VersionMin = SSL.TLS10;
+               VersionMax = SSL.TLS12;
+               AllowRenegotiation = true;
+               receivedNoReneg = false;
+               clientRandom = new byte[32];
+               serverRandom = new byte[32];
+               masterSecret = new byte[48];
+               HandshakeCount = 0;
+       }
+
+       /*
+        * If 'NormalizeIOError' is true, then I/O errors while writing
+        * on the underlying stream will be reported as a generic
+        * SSLException with message "Unexpected transport closure".
+        * This helps test code that expects an asynchronous abort that
+        * may be detected during a read of a write operation, depending
+        * on the exact timing. Default is false.
+        */
+       public bool NormalizeIOError {
+               get {
+                       return outRec.NormalizeIOError;
+               }
+               set {
+                       outRec.NormalizeIOError = value;
+               }
+       }
+
+       /*
+        * If 'AutoFlush' is true, then after every Write() or WriteByte()
+        * call, the current record is assembled and sent, leaving no
+        * buffered data yet to be sent. Default value is true.
+        */
+       public bool AutoFlush {
+               get; set;
+       }
+
+       /*
+        * If 'CloseSub' is true, then the underlying transport stream
+        * will be closed on normal closure or protocol failure. Default
+        * value is true. If a closure callback is set in 'OnClose',
+        * then this flag is ignored.
+        */
+       public bool CloseSub {
+               get; set;
+       }
+
+       /*
+        * A generic callback to be invoked when the SSL connection is
+        * closed. If a callback is specified here, then the 'CloseSub'
+        * flag is ignored; the callback is supposed to handle the
+        * closing of the transport stream, if necessary.
+        */
+       public delegate void CloseGen(Stream sub);
+       public CloseGen OnClose {
+               get; set;
+       }
+
+       /*
+        * If 'NoCloseNotify' is true, then lack of a close_notify from
+        * the peer before closing the transport stream will NOT be
+        * considered erroneous; i.e. it won't trigger an exception.
+        * If that situation arises, the ClosedWithoutNotify flag will
+        * return true, so the caller may still test for it.
+        *
+        * Not sending close_notify alerts before closing is a widespread
+        * practice, since it simplifies timeout management. It is unsafe,
+        * in that it allows truncation attacks, unless the application
+        * protocol is self-terminated (e.g. HTTP/1.1 is self-terminated,
+        * HTTP/0.9 is not).
+        *
+        * Default value is false: lack of close_notify triggers an
+        * exception.
+        */
+       public bool NoCloseNotify {
+               get; set;
+       }
+
+       /*
+        * The 'ClosedWithoutNotify' flag is set to true if the connection
+        * was closed abruptly (no close_notify alert), but this engine
+        * was configured to tolerate that situation ('NoCloseNotify' was
+        * set to true).
+        */
+       public bool ClosedWithoutNotify {
+               get; private set;
+       }
+
+       /*
+        * Maximum allowed size for a handshake message. The protocol
+        * allows for messages up to 16 megabytes, but these hardly
+        * make sense in practice. In the interest of avoiding
+        * memory-based denials of service, a lower limit can be set.
+        * If an incoming handshake message exceeds that size, then
+        * an exception is thrown. Default value is 65536.
+        */
+       public int MaximumHandshakeMessageLength {
+               get; set;
+       }
+
+       /*
+        * Set the cipher suites supported by this engine, in preference
+        * order (most preferred comes first). Default value should ensure
+        * maximum interoperability and good security and performance.
+        */
+       public int[] SupportedCipherSuites {
+               get; set;
+       }
+
+       /*
+        * Set the elliptic curves supported by this engine, for ECDH
+        * and ECDHE. The list is in preference order (most preferred
+        * comes first). Default list is Curve25519 followed by the
+        * usual NIST curves (P-256, P-384 and P-521, in that order).
+        */
+       public int[] SupportedCurves {
+               get; set;
+       }
+
+       /*
+        * Set the supported hash and sign algorithm combinations. Each
+        * value is a 16-bit integer: high byte is the hash algorithm
+        * identifier, while low byte is the signature algorithm identifier.
+        * List is ordered by preference order. Default list applies the
+        * following rules:
+        *
+        *  - Hash function order is:
+        *    SHA-256, SHA-224, SHA-384, SHA-512, SHA-1
+        *
+        *  - For the same hash function, ECDSA is preferred over RSA.
+        *
+        * Note that the special RSA with MD5+SHA-1 shall not be part of
+        * this list. Its use is implicit when using TLS 1.0 or 1.1 with
+        * a cipher suite or client authentication that uses RSA signatures.
+        */
+       public int[] SupportedHashAndSign {
+               get; set;
+       }
+
+       /*
+        * Set the minimum supported protocol version. Default is TLS 1.0.
+        */
+       public int VersionMin {
+               get {
+                       return versionMin;
+               }
+               set {
+                       SetOutputRecordVersion(value);
+                       versionMin = value;
+               }
+       }
+
+       /*
+        * Set the maximum supported protocol version. Default is TLS 1.2.
+        */
+       public int VersionMax {
+               get; set;
+       }
+
+       /*
+        * Get the actual protocol version. This is set only when the
+        * version is chosen, within the handshake.
+        */
+       public int Version {
+               get {
+                       return actualVersion;
+               }
+               internal set {
+                       actualVersion = value;
+                       SetOutputRecordVersion(value);
+               }
+       }
+
+       /*
+        * Get the actual cipher suite. This is set only when it is chosen,
+        * within the handshake.
+        */
+       public int CipherSuite {
+               get; internal set;
+       }
+
+       /*
+        * Get or set the server name associated with this connection.
+        *
+        * On a client, the caller shall set that name; if non-null and
+        * non-empty, then it will be sent to the server as a SNI
+        * extension; it will moreover be matched against the names
+        * found in the server's certificate.
+        *
+        * On a server, this value is set to the host name received from
+        * the client as a SNI extension (if any).
+        */
+       public string ServerName {
+               get; set;
+       }
+
+       /*
+        * Get the current session parameters. If the initial handshake
+        * was not performed yet, then the handshake is performed now
+        * (thus, this call may trigger an exception in case of I/O or
+        * protocol error).
+        *
+        * The returned object is a freshly allocated copy, which is not
+        * impacted by further activity on this engine.
+        */
+       public SSLSessionParameters SessionParameters {
+               get {
+                       if (state == STATE_HANDSHAKE) {
+                               DoHandshakeWrapper();
+                       }
+                       return new SSLSessionParameters(sessionID, Version,
+                               CipherSuite, ServerName, masterSecret);
+               }
+       }
+
+       /*
+        * Renegotiation support: if true, then renegotiations will be
+        * accepted (both explicit calls to Renegotiate(), and requests
+        * from the peer); if false, then all renegotiation attempts will
+        * be rejected.
+        *
+        * Default value is true. Regardless of the value of this flag,
+        * renegotiation attempts will be denied if the peer does not
+        * support the "Secure Renegotiation" extension (RFC 5746).
+        */
+       public bool AllowRenegotiation {
+               get; set;
+       }
+
+       /*
+        * Set "quirks" to alter engine behaviour. When null (which is the
+        * default), normal behaviour occurs.
+        */
+       public SSLQuirks Quirks {
+               get; set;
+       }
+
+       /*
+        * Get the current handshake count. This starts at 0 (before
+        * the initial handshake) and is incremented for each handshake.
+        * The increment occurs at the start of the handshake (after
+        * confirmation that a handshake will be indeed attempted, when
+        * doing a renegotiation).
+        */
+       public long HandshakeCount {
+               get; internal set;
+       }
+
+       /*
+        * Tell whether the last handshake was a session resumption or not.
+        * This flag is set at the end of the handshake.
+        */
+       public bool IsResume {
+               get; internal set;
+       }
+
+       /*
+        * Trigger a new handshake. If the initial handshake was not done
+        * yet, then it is performed at that point. Otherwise, this is
+        * a renegotiation attempt.
+        *
+        * Returned value is true if a new handshake happened, false
+        * otherwise. For the initial handshake, true is always returned
+        * (handshake failures trigger exceptions). For a renegotiation,
+        * a 'false' value may be returned if one of the following holds:
+        *  - This engine was configured not to use renegotiations.
+        *  - The peer does not support secure renegotiation.
+        *  - A renegotiation was attempted, but denied by the peer.
+        */
+       public bool Renegotiate()
+       {
+               if (!FirstHandshakeDone) {
+                       DoHandshakeWrapper();
+                       return true;
+               }
+
+               if (!AllowRenegotiation) {
+                       return false;
+               }
+               if (!GetQuirkBool("noSecureReneg") && renegSupport < 0) {
+                       return false;
+               }
+               int rt = outRec.RecordType;
+               try {
+                       PrepareRenegotiate();
+               } catch {
+                       MarkFailed();
+                       throw;
+               }
+               if (!DoHandshakeWrapper()) {
+                       outRec.RecordType = rt;
+                       return false;
+               }
+               return true;
+       }
+
+       /* ============================================================ */
+       /*
+        * Stream standard API.
+        */
+
+       public override int ReadByte()
+       {
+               if (state == STATE_CLOSED) {
+                       return -1;
+               }
+               CheckAppData();
+               try {
+                       return ZRead();
+               } catch {
+                       MarkFailed();
+                       throw;
+               }
+       }
+
+       public override int Read(byte[] buf, int off, int len)
+       {
+               if (state == STATE_CLOSED) {
+                       return -1;
+               }
+               CheckAppData();
+               try {
+                       return ZRead(buf, off, len);
+               } catch {
+                       MarkFailed();
+                       throw;
+               }
+       }
+
+       public override void WriteByte(byte x)
+       {
+               CheckAppData();
+               try {
+                       outRec.Write(x);
+                       if (AutoFlush) {
+                               outRec.Flush();
+                       }
+               } catch {
+                       MarkFailed();
+                       throw;
+               }
+       }
+
+       public override void Write(byte[] buf, int off, int len)
+       {
+               CheckAppData();
+               try {
+                       outRec.Write(buf, off, len);
+                       if (AutoFlush) {
+                               outRec.Flush();
+                       }
+               } catch {
+                       MarkFailed();
+                       throw;
+               }
+       }
+
+       public override void Flush()
+       {
+               CheckAppData();
+               try {
+                       outRec.Flush();
+               } catch {
+                       MarkFailed();
+                       throw;
+               }
+       }
+
+       public override void Close()
+       {
+               Close(true);
+       }
+
+       void Close(bool expectCloseNotify)
+       {
+               if (state == STATE_CLOSED) {
+                       return;
+               }
+               try {
+                       if (state == STATE_APPDATA) {
+                               SendWarning(SSL.CLOSE_NOTIFY);
+                               state = STATE_CLOSING;
+                               if (expectCloseNotify) {
+                                       if (!NextRecord()) {
+                                               return;
+                                       }
+                                       throw new SSLException(
+                                               "Peer does not want to close");
+                               }
+                       }
+               } catch {
+                       // ignored
+               } finally {
+                       MarkFailed();
+               }
+       }
+
+       public override long Seek(long off, SeekOrigin origin)
+       {
+               throw new NotSupportedException();
+       }
+
+       public override void SetLength(long len)
+       {
+               throw new NotSupportedException();
+       }
+
+       public override bool CanRead {
+               get {
+                       return state != STATE_CLOSED;
+               }
+       }
+
+       public override bool CanWrite {
+               get {
+                       return state != STATE_CLOSED;
+               }
+       }
+
+       public override bool CanSeek {
+               get {
+                       return false;
+               }
+       }
+
+       public override long Length {
+               get {
+                       throw new NotSupportedException();
+               }
+       }
+
+       public override long Position {
+               get {
+                       throw new NotSupportedException();
+               }
+               set {
+                       throw new NotSupportedException();
+               }
+       }
+
+       /* ============================================================ */
+
+       /*
+        * Test whether this engine is a client or a server.
+        */
+       internal abstract bool IsClient {
+               get;
+       }
+
+       /*
+        * Test the configuration of hash-and-sign with regards to
+        * cipher suites: if the list of cipher suites includes an
+        * ECDHE suite, then there must be at least one supported
+        * hash-and-sign with the corresponding signature type.
+        */
+       internal void CheckConfigHashAndSign()
+       {
+               /*
+                * The hash-and-sign are only for TLS 1.2.
+                */
+               if (VersionMax < SSL.TLS12) {
+                       return;
+               }
+
+               /*
+                * If the list is empty then we will work over the default
+                * list inferred by the peer (no extension sent).
+                */
+               if (SupportedHashAndSign == null
+                       || SupportedHashAndSign.Length == 0)
+               {
+                       return;
+               }
+
+               bool needRSA = false;
+               bool needECDSA = false;
+               foreach (int cs in SupportedCipherSuites) {
+                       if (SSL.IsECDHE_RSA(cs)) {
+                               needRSA = true;
+                       }
+                       if (SSL.IsECDHE_ECDSA(cs)) {
+                               needECDSA = true;
+                       }
+               }
+               foreach (int hs in SupportedHashAndSign) {
+                       int sa = hs & 0xFF;
+                       if (needRSA && sa == SSL.RSA) {
+                               needRSA = false;
+                       }
+                       if (needECDSA && sa == SSL.ECDSA) {
+                               needECDSA = false;
+                       }
+               }
+               if (needRSA) {
+                       throw new SSLException("Incoherent configuration:"
+                               + " supports ECDHE_RSA but no RSA signature"
+                               + " (for TLS 1.2)");
+               }
+               if (needECDSA) {
+                       throw new SSLException("Incoherent configuration:"
+                               + " supports ECDHE_ECDSA but no ECDSA signature"
+                               + " (for TLS 1.2)");
+               }
+       }
+
+       internal bool HasQuirk(string name)
+       {
+               return Quirks != null && Quirks.GetString(name, null) != null;
+       }
+
+       internal bool GetQuirkBool(string name)
+       {
+               return GetQuirkBool(name, false);
+       }
+
+       internal bool GetQuirkBool(string name, bool defaultValue)
+       {
+               if (Quirks == null) {
+                       return false;
+               }
+               return Quirks.GetBoolean(name, defaultValue);
+       }
+
+       internal int GetQuirkInt(string name)
+       {
+               return GetQuirkInt(name, 0);
+       }
+
+       internal int GetQuirkInt(string name, int defaultValue)
+       {
+               if (Quirks == null) {
+                       return defaultValue;
+               }
+               return Quirks.GetInteger(name, defaultValue);
+       }
+
+       internal string GetQuirkString(string name)
+       {
+               return GetQuirkString(name, null);
+       }
+
+       internal string GetQuirkString(string name, string defaultValue)
+       {
+               if (Quirks == null) {
+                       return defaultValue;
+               }
+               return Quirks.GetString(name, defaultValue);
+       }
+
+       /*
+        * Test whether the first handshake has been done or not.
+        */
+       internal bool FirstHandshakeDone {
+               get {
+                       return savedClientFinished != null;
+               }
+       }
+
+       /*
+        * Close the engine. No I/O may happen beyond this call.
+        */
+       internal void MarkFailed()
+       {
+               if (sub != null) {
+                       try {
+                               if (OnClose != null) {
+                                       OnClose(sub);
+                               } else if (CloseSub) {
+                                       sub.Close();
+                               }
+                       } catch {
+                               // ignored
+                       }
+                       sub = null;
+               }
+               state = STATE_CLOSED;
+       }
+
+       /*
+        * Check that the current state is not closed.
+        */
+       void CheckNotClosed()
+       {
+               if (state == STATE_CLOSED) {
+                       throw new SSLException("Connection is closed");
+               }
+       }
+
+       /*
+        * Check that we are ready to exchange application data. A
+        * handshake is performed if necessary.
+        */
+       void CheckAppData()
+       {
+               CheckNotClosed();
+               if (!FirstHandshakeDone) {
+                       DoHandshakeWrapper();
+               } else if (state != STATE_APPDATA) {
+                       throw new SSLException(
+                               "Connection not ready for application data");
+               }
+       }
+
+       /*
+        * Set the version for outgoing records.
+        */
+       internal void SetOutputRecordVersion(int version)
+       {
+               outRec.SetVersion(version);
+       }
+
+       /*
+        * Set the expected version for incoming records. This should be
+        * used by the server code just after parsing the ClientHello,
+        * because the client is supposed to send records matching the
+        * protocol version decided by the server and sent in the
+        * ServerHello.
+        *
+        * For a SSL client, this call in unnecessary because default
+        * behaviour is to look at the version of the first incoming
+        * record (containing the ServerHello for the server) and expect
+        * all subsequent records to have the same version.
+        */
+       internal void SetInputRecordVersion(int version)
+       {
+               inRec.SetExpectedVersion(version);
+       }
+
+       /*
+        * Flush the underlying record engine.
+        */
+       internal void FlushSub()
+       {
+               outRec.Flush();
+       }
+
+       /*
+        * Get next record. This returns false only if the connection
+        * turned out to be ended "properly".
+        *
+        * In all other cases, a record is obtained, and true is
+        * returned. It is possible that the record contains no unread
+        * data (it could be an empty record, or it could be an alert
+        * record whose contents are automatically processed).
+        */
+       internal bool NextRecord()
+       {
+               if (!inRec.NextRecord()) {
+                       if (NoCloseNotify && (state == STATE_APPDATA
+                               || state == STATE_CLOSING))
+                       {
+                               /*
+                                * No close_notify, but we have been set
+                                * to tolerate it.
+                                */
+                               ClosedWithoutNotify = true;
+                               MarkFailed();
+                               return false;
+                       }
+                       throw new SSLException("Unexpected transport closure");
+               }
+
+               /*
+                * We basically ignore empty records, regardless of state.
+                */
+               if (inRec.BufferedLength == 0) {
+                       return true;
+               }
+
+               int rt = inRec.RecordType;
+               switch (rt) {
+
+               case SSL.ALERT:
+                       /*
+                        * Fatal alerts trigger an exception. Warnings are
+                        * ignored, except close_notify and no_renegotiation.
+                        */
+                       while (inRec.BufferedLength > 0) {
+                               int level = deferredAlert;
+                               deferredAlert = -1;
+                               if (level < 0) {
+                                       level = inRec.Read();
+                                       if (inRec.BufferedLength == 0) {
+                                               deferredAlert = level;
+                                               break;
+                                       }
+                               }
+                               int desc = inRec.Read();
+                               if (level == SSL.FATAL) {
+                                       throw new SSLException(desc);
+                               }
+                               if (level != SSL.WARNING) {
+                                       throw new SSLException("Unknown"
+                                               + " alert level: " + level);
+                               }
+                               if (desc == SSL.CLOSE_NOTIFY) {
+                                       if (state == STATE_CLOSING) {
+                                               MarkFailed();
+                                               return false;
+                                       }
+                                       if (state != STATE_APPDATA) {
+                                               throw new SSLException(
+                                                       "Unexpected closure");
+                                       }
+                                       Close(false);
+                                       return false;
+                               } else if (desc == SSL.NO_RENEGOTIATION) {
+                                       receivedNoReneg = true;
+                               }
+                       }
+                       return true;
+
+               case SSL.HANDSHAKE:
+                       switch (state) {
+                       case STATE_HANDSHAKE:
+                               return true;
+                       case STATE_APPDATA:
+                               ProcessExtraHandshakeWrapper();
+                               return true;
+                       }
+                       throw new SSLException("Unexpected handshake message");
+
+               case SSL.CHANGE_CIPHER_SPEC:
+                       if (state == STATE_CCS) {
+                               return true;
+                       }
+                       throw new SSLException("Unexpected Change Cipher Spec");
+
+               case SSL.APPLICATION_DATA:
+                       if (state == STATE_APPDATA) {
+                               return true;
+                       }
+                       throw new SSLException("Unexpected application data");
+
+               default:
+                       throw new SSLException("Invalid record type: " + rt);
+
+               }
+       }
+
+       /*
+        * ZRead() reads the next byte, possibly obtaining further records
+        * to do so. It may return -1 only if the end-of-stream was reached,
+        * which can happen only in "application data" state (after a
+        * successful handshake).
+        */
+       int ZRead()
+       {
+               int x = ZReadNoHash();
+               if (x >= 0 && inRec.RecordType == SSL.HANDSHAKE) {
+                       HashExtra((byte)x);
+               }
+               return x;
+       }
+
+       /*
+        * ZReadNoHash() is similar to ZRead() except that it skips
+        * the automatic hashing of handshake messages.
+        */
+       int ZReadNoHash()
+       {
+               while (inRec.BufferedLength == 0) {
+                       if (!NextRecord()) {
+                               return -1;
+                       }
+               }
+               return inRec.Read();
+       }
+
+       /*
+        * ZReadNoHashNoReneg() is similar to ZReadNoHash() except
+        * that it may return -1 while being in state STATE_HANDSHAKE
+        * in case a no_renegotiation alert is received.
+        */
+       int ZReadNoHashNoReneg()
+       {
+               while (inRec.BufferedLength == 0) {
+                       receivedNoReneg = false;
+                       if (!NextRecord() || receivedNoReneg) {
+                               return -1;
+                       }
+               }
+               return inRec.Read();
+       }
+
+       /*
+        * Read some bytes. At least one byte will be obtained, unless
+        * EOF is reached. Extra records are obtained if necessary.
+        */
+       int ZRead(byte[] buf)
+       {
+               return ZRead(buf, 0, buf.Length);
+       }
+
+       /*
+        * Read some bytes. At least one byte will be obtained, unless
+        * EOF is reached. Extra records are obtained if necessary.
+        */
+       int ZRead(byte[] buf, int off, int len)
+       {
+               while (inRec.BufferedLength == 0) {
+                       if (!NextRecord()) {
+                               return 0;
+                       }
+               }
+               int rlen = inRec.Read(buf, off, len);
+               if (rlen > 0 && inRec.RecordType == SSL.HANDSHAKE) {
+                       md5.Update(buf, off, rlen);
+                       sha1.Update(buf, off, rlen);
+                       sha256.Update(buf, off, rlen);
+                       sha384.Update(buf, off, rlen);
+               }
+               return rlen;
+       }
+
+       bool DoHandshakeWrapper()
+       {
+               /*
+                * Record split mode syntax:  name:[types]
+                *
+                * 'name' is a symbolic name.
+                *
+                * 'types' is a comma-separated list of record types on
+                *  which the splitting mode applies. Record types are
+                *  numeric values (in decimal).
+                */
+               string splitMode = GetQuirkString("recordSplitMode");
+               if (splitMode != null) {
+                       splitMode = splitMode.Trim();
+                       int j = splitMode.IndexOf(':');
+                       int m = 0;
+                       if (j >= 0) {
+                               string w = splitMode.Substring(j + 1);
+                               foreach (string s in w.Split(',')) {
+                                       m |= 1 << Int32.Parse(s.Trim());
+                               }
+                               splitMode = splitMode.Substring(0, j).Trim();
+                       }
+                       switch (splitMode.ToLowerInvariant()) {
+                       case "half":
+                               m |= OutputRecord.MODE_SPLIT_HALF;
+                               break;
+                       case "zero_before":
+                               m |= OutputRecord.MODE_SPLIT_ZERO_BEFORE;
+                               break;
+                       case "zero_half":
+                               m |= OutputRecord.MODE_SPLIT_ZERO_HALF;
+                               break;
+                       case "one_start":
+                               m |= OutputRecord.MODE_SPLIT_ONE_START;
+                               break;
+                       case "one_end":
+                               m |= OutputRecord.MODE_SPLIT_ONE_END;
+                               break;
+                       case "multi_one":
+                               m |= OutputRecord.MODE_SPLIT_MULTI_ONE;
+                               break;
+                       default:
+                               throw new SSLException(string.Format(
+                                       "Bad recordSplitMode name: '{0}'",
+                                       splitMode));
+                       }
+                       outRec.SetSplitMode(m);
+               }
+
+               /*
+                * Triggers for extra empty records.
+                */
+               outRec.SetThresholdZeroHandshake(
+                       GetQuirkInt("thresholdZeroHandshake"));
+               outRec.SetThresholdZeroAppData(
+                       GetQuirkInt("thresholdZeroAppData"));
+
+               try {
+                       for (;;) {
+                               bool ret = DoHandshake();
+                               if (!ret) {
+                                       return false;
+                               }
+                               /*
+                                * There could be some extra handshake
+                                * data lingering in the input buffer, in
+                                * which case we must process it right away.
+                                */
+                               if (HasBufferedHandshake) {
+                                       ProcessExtraHandshakeWrapper();
+                               }
+                               return true;
+                       }
+               } catch {
+                       MarkFailed();
+                       throw;
+               }
+       }
+
+       void ProcessExtraHandshakeWrapper()
+       {
+               try {
+                       while (HasBufferedHandshake) {
+                               ProcessExtraHandshake();
+                       }
+               } catch {
+                       MarkFailed();
+                       throw;
+               }
+       }
+
+       /*
+        * Set the state to the provided value.
+        */
+       internal void SetState(int state)
+       {
+               this.state = state;
+       }
+
+       /*
+        * Reset running hashes for handshake messages.
+        */
+       internal void ResetHashes()
+       {
+               md5.Reset();
+               sha1.Reset();
+               sha256.Reset();
+               sha384.Reset();
+       }
+
+       /*
+        * Inject a specific byte value in the hash functions for handshake
+        * messages.
+        */
+       void HashExtra(byte b)
+       {
+               md5.Update(b);
+               sha1.Update(b);
+               sha256.Update(b);
+               sha384.Update(b);
+       }
+
+       /*
+        * Run a handshake. This function normally returns true; it returns
+        * false only if the call was a renegotiation attempt AND it was
+        * denied by the peer.
+        */
+       internal abstract bool DoHandshake();
+
+       /*
+        * A non-empty handshake record has been received while we
+        * were in post-handshake "application data" state. This
+        * method should handle that message with the necessary
+        * actions; if it returns false then the caller will fail
+        * the connection with an exception.
+        */
+       internal abstract void ProcessExtraHandshake();
+
+       /*
+        * Perform the preparatory steps for a renegotiation. For a client,
+        * there are no such steps, so this call is a no-op. For a server,
+        * an HelloRequest should be sent.
+        */
+       internal abstract void PrepareRenegotiate();
+
+       /*
+        * Read the next handshake message. This also sets the state
+        * to STATE_HANDSHAKE.
+        */
+       internal byte[] ReadHandshakeMessage(out int msgType)
+       {
+               return ReadHandshakeMessage(out msgType, false);
+       }
+
+       /*
+        * Read the next handshake message. This also sets the state
+        * to STATE_HANDSHAKE. If tolerateNoReneg is true, then a
+        * received no_renegotiation alert interrupts the reading, in
+        * which case this function sets the state back to its previous
+        * value and returns null.
+        */
+       internal byte[] ReadHandshakeMessage(
+               out int msgType, bool tolerateNoReneg)
+       {
+               int oldState = state;
+               state = STATE_HANDSHAKE;
+
+               /*
+                * In STATE_HANDSHAKE, an unexpected closure is never
+                * tolerated, so ZRead() won't return -1.
+                */
+               for (;;) {
+                       msgType = ZReadNoHashNoReneg();
+                       if (msgType < 0) {
+                               if (tolerateNoReneg) {
+                                       state = oldState;
+                                       return null;
+                               }
+                               continue;
+                       }
+                       if (msgType == SSL.HELLO_REQUEST && IsClient) {
+                               /*
+                                * Extra HelloRequest messages are ignored
+                                * (as long as they are properly empty), and
+                                * they don't contribute to the running hashes.
+                                */
+                               if (ZReadNoHash() != 0
+                                       || ZReadNoHash() != 0
+                                       || ZReadNoHash() != 0)
+                               {
+                                       throw new SSLException(
+                                               "Non-empty HelloRequest");
+                               }
+                               continue;
+                       }
+                       HashExtra((byte)msgType);
+                       int len = ZRead();
+                       len = (len << 8) + ZRead();
+                       len = (len << 8) + ZRead();
+                       if (len > MaximumHandshakeMessageLength) {
+                               throw new SSLException(
+                                       "Oversized handshake message: len="
+                                       + len);
+                       }
+                       byte[] buf = new byte[len];
+                       int off = 0;
+                       while (off < len) {
+                               off += ZRead(buf, off, len - off);
+                       }
+                       return buf;
+               }
+       }
+
+       /*
+        * Read the next handshake message; fail (with an exception) if
+        * it does not have the specified type. This also sets the state
+        * to STATE_HANDSHAKE.
+        */
+       internal byte[] ReadHandshakeMessageExpected(int msgType)
+       {
+               int rmt;
+               byte[] msg = ReadHandshakeMessage(out rmt);
+               if (rmt != msgType) {
+                       throw new SSLException(string.Format("Unexpected"
+                               + " handshake message {0} (expected: {1})",
+                               rmt, msgType));
+               }
+               return msg;
+       }
+
+       /*
+        * Read an HelloRequest message. If, after reading an HelloRequest,
+        * the record is not empty, then other HelloRequest messages are
+        * read.
+        *
+        * This method shall be called only when a non-empty record of type
+        * handshake is buffered. It switches the state to STATE_HANDSHAKE.
+        */
+       internal void ReadHelloRequests()
+       {
+               state = STATE_HANDSHAKE;
+               while (inRec.BufferedLength > 0) {
+                       int x = ZReadNoHash();
+                       if (x != SSL.HELLO_REQUEST) {
+                               throw new SSLException(
+                                       "Unexpected handshake message");
+                       }
+                       if (ZReadNoHash() != 0x00
+                               || ZReadNoHash() != 0x00
+                               || ZReadNoHash() != 0x00)
+                       {
+                               throw new SSLException(
+                                       "Non-empty HelloRequest");
+                       }
+               }
+       }
+
+       /*
+        * Test whether there is some buffered handshake data.
+        */
+       internal bool HasBufferedHandshake {
+               get {
+                       return inRec.RecordType == SSL.HANDSHAKE
+                               && inRec.BufferedLength > 0;
+               }
+       }
+
+       static DateTime EPOCH =
+               new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+       /*
+        * Create a new client or server random.
+        */
+       internal void MakeRandom(byte[] dst)
+       {
+               uint utc = (uint)((DateTime.UtcNow - EPOCH).Ticks / 10000000);
+               IO.Enc32be(utc, dst, 0);
+               RNG.GetBytes(dst, 4, dst.Length - 4);
+       }
+
+       /*
+        * Create a MemoryStream with preloaded 4-byte handshake message
+        * header.
+        */
+       internal MemoryStream StartHandshakeMessage(int type)
+       {
+               MemoryStream ms = new MemoryStream();
+               ms.WriteByte((byte)type);
+               IO.Write24(ms, 0);
+               return ms;
+       }
+
+       /*
+        * Finalise a handshake message, and send it.
+        */
+       internal void EndHandshakeMessage(MemoryStream ms)
+       {
+               byte[] buf = ms.ToArray();
+               IO.Enc24be(buf.Length - 4, buf, 1);
+               outRec.RecordType = SSL.HANDSHAKE;
+               outRec.Write(buf);
+               md5.Update(buf);
+               sha1.Update(buf);
+               sha256.Update(buf);
+               sha384.Update(buf);
+       }
+
+       /*
+        * Get the PRF corresponding to the negotiated protocol version
+        * and cipher suite.
+        */
+       internal PRF GetPRF()
+       {
+               if (Version <= SSL.TLS11) {
+                       return new PRF();
+               } else {
+                       return SSL.GetPRFForTLS12(CipherSuite);
+               }
+       }
+
+       /*
+        * Compute the master secret from the provided premaster secret.
+        */
+       internal void ComputeMaster(byte[] pms)
+       {
+               PRF prf = GetPRF();
+               byte[] seed = new byte[64];
+               Array.Copy(clientRandom, 0, seed, 0, 32);
+               Array.Copy(serverRandom, 0, seed, 32, 32);
+               prf.GetBytes(pms, PRF.LABEL_MASTER_SECRET, seed, masterSecret);
+       }
+
+       /*
+        * Set the master secret to the provided value. This is used when
+        * resuming a session.
+        */
+       internal void SetMasterSecret(byte[] rms)
+       {
+               Array.Copy(rms, 0, masterSecret, 0, rms.Length);
+       }
+
+       /*
+        * Switch to new security parameters.
+        * 'write' is true if we switch encryption for our sending channel,
+        * false for our receiving channel.
+        */
+       internal void SwitchEncryption(bool write)
+       {
+               int macLen, encLen, ivLen;
+               IBlockCipher block = null;
+               IDigest hash = null;
+               Poly1305 pp = null;
+               switch (CipherSuite) {
+               case SSL.RSA_WITH_3DES_EDE_CBC_SHA:
+               case SSL.DH_DSS_WITH_3DES_EDE_CBC_SHA:
+               case SSL.DH_RSA_WITH_3DES_EDE_CBC_SHA:
+               case SSL.DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+               case SSL.DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+               case SSL.DH_anon_WITH_3DES_EDE_CBC_SHA:
+               case SSL.ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+               case SSL.ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+               case SSL.ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+               case SSL.ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+               case SSL.ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+                       macLen = 20;
+                       encLen = 24;
+                       ivLen = 8;
+                       block = new DES();
+                       hash = new SHA1();
+                       break;
+
+               case SSL.RSA_WITH_AES_128_CBC_SHA:
+               case SSL.DH_DSS_WITH_AES_128_CBC_SHA:
+               case SSL.DH_RSA_WITH_AES_128_CBC_SHA:
+               case SSL.DHE_DSS_WITH_AES_128_CBC_SHA:
+               case SSL.DHE_RSA_WITH_AES_128_CBC_SHA:
+               case SSL.DH_anon_WITH_AES_128_CBC_SHA:
+               case SSL.ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+               case SSL.ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+               case SSL.ECDH_RSA_WITH_AES_128_CBC_SHA:
+               case SSL.ECDHE_RSA_WITH_AES_128_CBC_SHA:
+               case SSL.ECDH_anon_WITH_AES_128_CBC_SHA:
+                       macLen = 20;
+                       encLen = 16;
+                       ivLen = 16;
+                       block = new AES();
+                       hash = new SHA1();
+                       break;
+
+               case SSL.RSA_WITH_AES_256_CBC_SHA:
+               case SSL.DH_DSS_WITH_AES_256_CBC_SHA:
+               case SSL.DH_RSA_WITH_AES_256_CBC_SHA:
+               case SSL.DHE_DSS_WITH_AES_256_CBC_SHA:
+               case SSL.DHE_RSA_WITH_AES_256_CBC_SHA:
+               case SSL.DH_anon_WITH_AES_256_CBC_SHA:
+               case SSL.ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+               case SSL.ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+               case SSL.ECDH_RSA_WITH_AES_256_CBC_SHA:
+               case SSL.ECDHE_RSA_WITH_AES_256_CBC_SHA:
+               case SSL.ECDH_anon_WITH_AES_256_CBC_SHA:
+                       macLen = 20;
+                       encLen = 32;
+                       ivLen = 16;
+                       block = new AES();
+                       hash = new SHA1();
+                       break;
+
+               case SSL.RSA_WITH_AES_128_CBC_SHA256:
+               case SSL.DH_DSS_WITH_AES_128_CBC_SHA256:
+               case SSL.DH_RSA_WITH_AES_128_CBC_SHA256:
+               case SSL.DHE_DSS_WITH_AES_128_CBC_SHA256:
+               case SSL.DHE_RSA_WITH_AES_128_CBC_SHA256:
+               case SSL.DH_anon_WITH_AES_128_CBC_SHA256:
+               case SSL.ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+               case SSL.ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+               case SSL.ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+               case SSL.ECDH_RSA_WITH_AES_128_CBC_SHA256:
+                       macLen = 32;
+                       encLen = 16;
+                       ivLen = 16;
+                       block = new AES();
+                       hash = new SHA256();
+                       break;
+
+               case SSL.RSA_WITH_AES_256_CBC_SHA256:
+               case SSL.DH_DSS_WITH_AES_256_CBC_SHA256:
+               case SSL.DH_RSA_WITH_AES_256_CBC_SHA256:
+               case SSL.DHE_DSS_WITH_AES_256_CBC_SHA256:
+               case SSL.DHE_RSA_WITH_AES_256_CBC_SHA256:
+               case SSL.DH_anon_WITH_AES_256_CBC_SHA256:
+                       macLen = 32;
+                       encLen = 32;
+                       ivLen = 16;
+                       block = new AES();
+                       hash = new SHA256();
+                       break;
+
+               case SSL.ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+               case SSL.ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+               case SSL.ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+               case SSL.ECDH_RSA_WITH_AES_256_CBC_SHA384:
+                       macLen = 48;
+                       encLen = 32;
+                       ivLen = 16;
+                       block = new AES();
+                       hash = new SHA384();
+                       break;
+
+               case SSL.RSA_WITH_AES_128_GCM_SHA256:
+               case SSL.DHE_RSA_WITH_AES_128_GCM_SHA256:
+               case SSL.DH_RSA_WITH_AES_128_GCM_SHA256:
+               case SSL.DHE_DSS_WITH_AES_128_GCM_SHA256:
+               case SSL.DH_DSS_WITH_AES_128_GCM_SHA256:
+               case SSL.DH_anon_WITH_AES_128_GCM_SHA256:
+               case SSL.ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+               case SSL.ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+               case SSL.ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+               case SSL.ECDH_RSA_WITH_AES_128_GCM_SHA256:
+                       macLen = 0;
+                       encLen = 16;
+                       ivLen = 4;
+                       block = new AES();
+                       break;
+
+               case SSL.RSA_WITH_AES_256_GCM_SHA384:
+               case SSL.DHE_RSA_WITH_AES_256_GCM_SHA384:
+               case SSL.DH_RSA_WITH_AES_256_GCM_SHA384:
+               case SSL.DHE_DSS_WITH_AES_256_GCM_SHA384:
+               case SSL.DH_DSS_WITH_AES_256_GCM_SHA384:
+               case SSL.DH_anon_WITH_AES_256_GCM_SHA384:
+               case SSL.ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+               case SSL.ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+               case SSL.ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+               case SSL.ECDH_RSA_WITH_AES_256_GCM_SHA384:
+                       macLen = 0;
+                       encLen = 32;
+                       ivLen = 4;
+                       block = new AES();
+                       break;
+
+               case SSL.ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+               case SSL.ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+               case SSL.DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+                       macLen = 0;
+                       encLen = 32;
+                       ivLen = 12;
+                       pp = new Poly1305();
+                       pp.ChaCha = new ChaCha20();
+                       break;
+
+               default:
+                       throw new SSLException("Unsupported cipher suite");
+               }
+
+               /*
+                * Normally we don't need IV when using CBC+HMAC with
+                * TLS 1.1+.
+                */
+               if (Version >= SSL.TLS11 && hash != null) {
+                       ivLen = 0;
+               }
+
+               byte[] seed = new byte[64];
+               Array.Copy(serverRandom, 0, seed, 0, 32);
+               Array.Copy(clientRandom, 0, seed, 32, 32);
+               byte[] kb = GetPRF().GetBytes(masterSecret,
+                       PRF.LABEL_KEY_EXPANSION, seed,
+                       (macLen + encLen + ivLen) << 1);
+
+               /*
+                * Test whether we need the client write keys, or the
+                * server write keys.
+                */
+               bool clientWrite = (IsClient == write);
+               HMAC hm = null;
+               if (macLen > 0) {
+                       hm = new HMAC(hash);
+                       hm.SetKey(kb, clientWrite ? 0 : macLen, macLen);
+               }
+               if (block != null) {
+                       block.SetKey(kb, (macLen << 1)
+                               + (clientWrite ? 0 : encLen), encLen);
+               } else if (pp != null) {
+                       pp.ChaCha.SetKey(kb, (macLen << 1)
+                               + (clientWrite ? 0 : encLen), encLen);
+               }
+               byte[] iv = null;
+               if (ivLen > 0) {
+                       iv = new byte[ivLen];
+                       Array.Copy(kb, ((macLen + encLen) << 1)
+                               + (clientWrite ? 0 : ivLen), iv, 0, ivLen);
+               }
+
+               if (hm != null) {
+                       /*
+                        * CBC+HMAC cipher suite.
+                        */
+                       if (write) {
+                               outRec.SetEncryption(
+                                       new RecordEncryptCBC(block, hm, iv));
+                       } else {
+                               inRec.SetDecryption(
+                                       new RecordDecryptCBC(block, hm, iv));
+                       }
+               } else if (block != null) {
+                       /*
+                        * GCM cipher suite.
+                        */
+                       if (write) {
+                               outRec.SetEncryption(
+                                       new RecordEncryptGCM(block, iv));
+                       } else {
+                               inRec.SetDecryption(
+                                       new RecordDecryptGCM(block, iv));
+                       }
+               } else if (pp != null) {
+                       /*
+                        * ChaCha20 + Poly1305 cipher suite.
+                        */
+                       if (write) {
+                               outRec.SetEncryption(
+                                       new RecordEncryptChaPol(pp, iv));
+                       } else {
+                               inRec.SetDecryption(
+                                       new RecordDecryptChaPol(pp, iv));
+                       }
+               } else {
+                       throw new Exception("NYI");
+               }
+       }
+
+       /*
+        * Compute Finished message. The 'client' flag is set to true
+        * for the Finished message sent by the client, false for the
+        * Finished message sent by the server.
+        */
+       internal byte[] ComputeFinished(bool client)
+       {
+               PRF prf;
+               byte[] seed;
+               if (Version <= SSL.TLS11) {
+                       seed = new byte[36];
+                       md5.DoPartial(seed, 0);
+                       sha1.DoPartial(seed, 16);
+                       prf = new PRF();
+               } else if (SSL.IsSHA384(CipherSuite)) {
+                       seed = sha384.DoPartial();
+                       prf = new PRF(new SHA384());
+               } else {
+                       seed = sha256.DoPartial();
+                       prf = new PRF(new SHA256());
+               }
+               byte[] label = client
+                       ? PRF.LABEL_CLIENT_FINISHED
+                       : PRF.LABEL_SERVER_FINISHED;
+               return prf.GetBytes(masterSecret, label, seed, 12);
+       }
+
+       /*
+        * Send a ChangeCipherSpec, then a Finished message. This
+        * call implies switching to the new encryption parameters for
+        * the sending channel.
+        */
+       internal void SendCCSAndFinished()
+       {
+               outRec.RecordType = SSL.CHANGE_CIPHER_SPEC;
+               outRec.Write(0x01);
+               outRec.RecordType = SSL.HANDSHAKE;
+               SwitchEncryption(true);
+               byte[] fin = ComputeFinished(IsClient);
+               if (IsClient) {
+                       savedClientFinished = fin;
+               } else {
+                       savedServerFinished = fin;
+               }
+               MemoryStream ms = StartHandshakeMessage(SSL.FINISHED);
+               ms.Write(fin, 0, fin.Length);
+               EndHandshakeMessage(ms);
+       }
+
+       /*
+        * Receive a ChangeCipherSpec, then a Finished message. This
+        * call implies switching to the new encryption parameters for
+        * the receiving channel.
+        */
+       internal void ParseCCSAndFinished()
+       {
+               if (inRec.BufferedLength > 0) {
+                       throw new SSLException(
+                               "Buffered data while expecting CCS");
+               }
+               state = STATE_CCS;
+               int x = ZRead();
+               if (x != 0x01 || inRec.BufferedLength > 0) {
+                       throw new SSLException("Invalid CCS contents");
+               }
+               SwitchEncryption(false);
+               state = STATE_HANDSHAKE;
+               byte[] fin = ComputeFinished(!IsClient);
+               byte[] msg = ReadHandshakeMessageExpected(SSL.FINISHED);
+               if (!Eq(fin, msg)) {
+                       throw new SSLException("Wrong Finished value");
+               }
+               if (inRec.BufferedLength > 0) {
+                       throw new SSLException(
+                               "Extra handshake data after Finished message");
+               }
+               if (IsClient) {
+                       savedServerFinished = fin;
+               } else {
+                       savedClientFinished = fin;
+               }
+       }
+
+       /*
+        * Switch to "application data" state just after the handshake.
+        */
+       internal void SetAppData()
+       {
+               SetState(STATE_APPDATA);
+               outRec.RecordType = SSL.APPLICATION_DATA;
+       }
+
+       /*
+        * Send an alert of level "warning".
+        */
+       internal void SendWarning(int type)
+       {
+               int rt = outRec.RecordType;
+               outRec.RecordType = SSL.ALERT;
+               outRec.Write(SSL.WARNING);
+               outRec.Write((byte)type);
+               outRec.Flush();
+               outRec.RecordType = rt;
+       }
+
+       static bool Eq(byte[] a, byte[] b)
+       {
+               int n = a.Length;
+               if (n != b.Length) {
+                       return false;
+               }
+               int z = 0;
+               for (int i = 0; i < n; i ++) {
+                       z |= a[i] ^ b[i];
+               }
+               return z == 0;
+       }
+}
+
+}
diff --git a/SSLTLS/SSLException.cs b/SSLTLS/SSLException.cs
new file mode 100644 (file)
index 0000000..1c36564
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.IO;
+
+namespace SSLTLS {
+
+/*
+ * All SSL failures (invalid messages, failed decryption or MAC checks,
+ * received fatal alerts...) trigger an SSLException. Since SSLEngine
+ * is a Stream, this exception is made an extension of IOException.
+ */
+
+public class SSLException : IOException {
+
+       public SSLException(string msg) : base(msg)
+       {
+       }
+
+       public SSLException(string msg, Exception cause) : base(msg, cause)
+       {
+       }
+
+       public SSLException(Exception cause) : base(cause.Message, cause)
+       {
+       }
+
+       public SSLException(int alert)
+               : base(String.Format(
+                       "Fatal alert {0} received from peer", alert))
+       {
+       }
+}
+
+}
diff --git a/SSLTLS/SSLQuirks.cs b/SSLTLS/SSLQuirks.cs
new file mode 100644 (file)
index 0000000..2a025e3
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace SSLTLS {
+
+/*
+ * An SSLQuirks instance is a set of named parameters that can be
+ * applied to an SSL engine (client or server) and alter its behaviour.
+ * Some of these variants are rarely used options permitted by the
+ * standard; others are downright invalid, and are meant to verify that
+ * a peer implementation is properly reacting to malformed messages
+ * and other anomalous conditions.
+ *
+ * Parameter names and values are strings. Parameter names are
+ * case-sensitive. As a matter of convention:
+ *
+ *   - Boolean values are set as "true" or "false" string values.
+ *
+ *   - Integers are encoded in decimal or hexadecimal; hexadecimal
+ *     values have a leading "0x" header (or "-0x" for a negative
+ *     hexadecimal value).
+ */
+
+public class SSLQuirks {
+
+       /*
+        * When reading a parameter value, and the parameter is not
+        * defined, null is returned.
+        */
+       public string this[string name] {
+               get {
+                       string v;
+                       if (d.TryGetValue(name, out v)) {
+                               return v;
+                       } else {
+                               return null;
+                       }
+               }
+               set {
+                       d[name] = value;
+               }
+       }
+
+       IDictionary<string, string> d;
+
+       public SSLQuirks()
+       {
+               d = new SortedDictionary<string, string>(
+                       StringComparer.Ordinal);
+       }
+
+       /*
+        * Get a boolean quirk. If defined, then the boolean value is
+        * written in 'val' and true is returned; otherwise, 'val' is
+        * set to false, and false is returned.
+        */
+       public bool TryGetBoolean(string name, out bool val)
+       {
+               string s;
+               if (!d.TryGetValue(name, out s)) {
+                       val = false;
+                       return false;
+               }
+               switch (s) {
+               case "true":   val = true; return true;
+               case "false":  val = false; return true;
+               }
+               s = s.ToLowerInvariant();
+               switch (s) {
+               case "true":   val = true; return true;
+               case "false":  val = false; return true;
+               }
+               throw new Exception("Quirk value is not a boolean");
+       }
+
+       /*
+        * Get a boolean quirk. If undefined, the provided default value
+        * is returned.
+        */
+       public bool GetBoolean(string name, bool defaultValue)
+       {
+               bool val;
+               if (TryGetBoolean(name, out val)) {
+                       return val;
+               } else {
+                       return defaultValue;
+               }
+       }
+
+       /*
+        * Get an integer quirk. If defined, then the integer value is
+        * written in 'val' and true is returned; otherwise, 'val' is
+        * set to 0, and false is returned.
+        */
+       public bool TryGetInteger(string name, out int val)
+       {
+               string s;
+               if (!d.TryGetValue(name, out s)) {
+                       val = 0;
+                       return false;
+               }
+               bool neg = false;
+               if (s.StartsWith("-")) {
+                       neg = true;
+                       s = s.Substring(1);
+               }
+               int radix;
+               if (s.StartsWith("0x")) {
+                       radix = 16;
+                       s = s.Substring(2);
+               } else {
+                       radix = 10;
+               }
+               int acc = 0;
+               if (s.Length == 0) {
+                       throw new Exception("Quirk value is not an integer");
+               }
+               foreach (char c in s) {
+                       int x;
+                       if (c >= '0' && c <= '9') {
+                               x = c - '0';
+                       } else if (c >= 'A' && c <= 'F') {
+                               x = c - ('A' - 10);
+                       } else if (c >= 'a' && c <= 'f') {
+                               x = c - ('a' - 10);
+                       } else {
+                               throw new Exception(
+                                       "Quirk value is not an integer");
+                       }
+                       if (x >= radix) {
+                               throw new Exception(
+                                       "Quirk value is not an integer");
+                       }
+                       acc = (acc * radix) + x;
+               }
+               if (neg) {
+                       acc = -acc;
+               }
+               val = acc;
+               return true;
+       }
+
+       /*
+        * Get an integer quirk. If undefined, the provided default value
+        * is returned.
+        */
+       public int GetInteger(string name, int defaultValue)
+       {
+               int val;
+               if (TryGetInteger(name, out val)) {
+                       return val;
+               } else {
+                       return defaultValue;
+               }
+       }
+
+       /*
+        * Get a string quirk. If undefined, the provided default value
+        * is returned.
+        */
+       public string GetString(string name, string defaultValue)
+       {
+               string s;
+               if (d.TryGetValue(name, out s)) {
+                       return s;
+               } else {
+                       return defaultValue;
+               }
+       }
+}
+
+}
diff --git a/SSLTLS/SSLServer.cs b/SSLTLS/SSLServer.cs
new file mode 100644 (file)
index 0000000..6f39135
--- /dev/null
@@ -0,0 +1,1019 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+/*
+ * Class for a SSL server connection.
+ *
+ * An instance is created over a specified transport stream. An optional
+ * cache for session parameters can be provided, to support session
+ * resumption. The instance handles the connection but cannot be
+ * "revived" after the connection was closed (the session parameters,
+ * though, can be extracted and used with another instance).
+ */
+
+public class SSLServer : SSLEngine {
+
+       /*
+        * If true, then the server will enforce its own preference
+        * order for cipher suite selection; otherwise, it will follow
+        * the client's preferences. Default value is false.
+        */
+       public bool EnforceServerOrder {
+               get; set;
+       }
+
+       /*
+        * Server policy object, that selects cipher suite and certificate
+        * chain to send to client. Such a policy object MUST be set
+        * before the initial handshake takes place. This property is
+        * initialised to the value provided as second argument to the
+        * SSLServer constructor.
+        */
+       public IServerPolicy ServerPolicy {
+               get; set;
+       }
+
+       /*
+        * Optional session cache for SSL sessions. If null, then no
+        * cache is used. Default value is null.
+        */
+       public ISessionCache SessionCache {
+               get; set;
+       }
+
+       /*
+        * If this flag is set to true, then session resumption will be
+        * rejected; all handshakes will be full handshakes. Main
+        * intended usage is when a server wants to renegotiate and ask
+        * for a client certificate. Note that even if that flag is set,
+        * each session resulting from a full handshake is still pushed
+        * to the session cache (if configured in SessionCache).
+        * Default value is false, meaning that session resumption is
+        * allowed (but won't happen anyway if no session cache was
+        * set in SessionCache).
+        */
+       public bool NoResume {
+               get; set;
+       }
+
+       /*
+        * Get the maximum supported version announced by the client
+        * in its ClientHello message.
+        */
+       public int ClientVersionMax {
+               get; private set;
+       }
+
+       /*
+        * Get the list of hash and signature algorithms supported by the
+        * client. Each value is a 16-bit integer, with the high byte
+        * being the hash algorithm, and the low byte the signature
+        * algorithm.
+        *
+        * The list is trimmed to include only hash and signature algorithms
+        * that are supported by both client and server. It is ordered
+        * by client or server preference, depending on the value of
+        * the EnforceServerOrder flag.
+        *
+        * If the client did not send the dedicated extension, then the
+        * list is inferred from the sent cipher suite, as specified
+        * by RFC 5246, section 7.4.1.4.1.
+        */
+       public List<int> ClientHashAndSign {
+               get; internal set;
+       }
+
+       /*
+        * Get the list of elliptic curves supported by the client. Each
+        * entry is a 16-bit integer that identifies a named curve. The
+        * list is ordered by client preferences.
+        *
+        * If the client did not send the Supported Curves extension,
+        * then the list will be inferred to contain NIST P-256 only
+        * (if the client supports at least one ECDH, ECDHE or ECDSA
+        * cipher suite), or to be empty (if the client does not support
+        * any EC-based cipher suite).
+        */
+       public List<int> ClientCurves {
+               get; internal set;
+       }
+
+       /*
+        * Get the list of elliptic curves supported by both client and
+        * server. Each entry is a 16-bit integer that identifies a
+        * named curve. The list is ordered by preference (client or
+        * server, depending on configuration). This list is the one
+        * used for curve selection for ECDHE.
+        */
+       public List<int> CommonCurves {
+               get; internal set;
+       }
+
+       /*
+        * Get the list of cipher suites supported by the client. The
+        * order matches the configured preferences (client or server
+        * preference order, depending on the EnforceServerOrder flag).
+        * Moreover, the list is trimmed:
+        *
+        *  - Signalling cipher suites ("SCSV") have been removed.
+        *
+        *  - Only suites supported by both client and server are kept.
+        *
+        *  - Suites that require TLS 1.2 are omitted if the selected
+        *    protocol version is TLS 1.0 or 1.1.
+        *
+        *  - Suites that require client support for RSA signatures are
+        *    removed if there is no common support for RSA signatures.
+        *
+        *  - Suites that require client support for ECDSA signatures
+        *    are removed if there is no common support for ECDSA
+        *    signatures.
+        *
+        *  - ECDHE suites are removed if there is no common support for
+        *    elliptic curves.
+        */
+       public List<int> CommonCipherSuites {
+               get; internal set;
+       }
+
+       IServerChoices serverChoices;
+       ECCurve ecdheCurve;
+       byte[] ecdheSecret;
+
+       /*
+        * Create an SSL server instance, over the provided stream.
+        * The 'serverPolicy' parameter is used as initial value to
+        * the ServerPolicy property.
+        */
+       public SSLServer(Stream sub, IServerPolicy serverPolicy)
+               : base(sub)
+       {
+               EnforceServerOrder = false;
+               ServerPolicy = serverPolicy;
+       }
+
+       internal override bool IsClient {
+               get {
+                       return false;
+               }
+       }
+
+       internal override bool DoHandshake()
+       {
+               CheckConfigHashAndSign();
+
+               ResetHashes();
+               MakeRandom(serverRandom);
+
+               bool resume;
+               if (!ParseClientHello(out resume)) {
+                       return false;
+               }
+               HandshakeCount ++;
+               SetOutputRecordVersion(Version);
+               SetInputRecordVersion(Version);
+
+               if (resume) {
+                       SendServerHello();
+                       SendCCSAndFinished();
+                       FlushSub();
+                       ParseCCSAndFinished();
+                       SetAppData();
+                       IsResume = true;
+                       return true;
+               }
+
+               SendServerHello();
+               SendCertificate();
+               if (SSL.IsECDHE(CipherSuite)) {
+                       SendServerKeyExchange();
+               }
+               SendServerHelloDone();
+               FlushSub();
+
+               ParseClientKeyExchange();
+               ParseCCSAndFinished();
+               SendCCSAndFinished();
+               FlushSub();
+               SetAppData();
+               IsResume = false;
+               if (SessionCache != null) {
+                       SessionCache.Store(SessionParameters);
+               }
+               return true;
+       }
+
+       bool ParseClientHello(out bool resume)
+       {
+               resume = false;
+               int mt;
+               byte[] msg = ReadHandshakeMessage(out mt, FirstHandshakeDone);
+               if (msg == null) {
+                       /*
+                        * Client rejected renegotiation attempt. This cannot
+                        * happen if we are invoked from
+                        * ProcessExtraHandshake() because that method is
+                        * invoked only when there is buffered handshake
+                        * data.
+                        */
+                       return false;
+               }
+               if (mt != SSL.CLIENT_HELLO) {
+                       throw new SSLException(string.Format("Unexpected"
+                               + " handshake message {0} (expecting a"
+                               + " ClientHello)", mt));
+               }
+
+               /*
+                * Maximum protocol version supported by the client.
+                */
+               if (msg.Length < 35) {
+                       throw new SSLException("Invalid ClientHello");
+               }
+               ClientVersionMax = IO.Dec16be(msg, 0);
+               if (ClientVersionMax < VersionMin) {
+                       throw new SSLException(string.Format(
+                               "No acceptable version (client max = 0x{0:X4})",
+                               ClientVersionMax));
+               }
+
+               /*
+                * Client random (32 bytes).
+                */
+               Array.Copy(msg, 2, clientRandom, 0, 32);
+
+               /*
+                * Session ID sent by the client: at most 32 bytes.
+                */
+               int idLen = msg[34];
+               int off = 35;
+               if (idLen > 32 || (off + idLen) > msg.Length) {
+                       throw new SSLException("Invalid ClientHello");
+               }
+               byte[] clientSessionID = new byte[idLen];
+               Array.Copy(msg, off, clientSessionID, 0, idLen);
+               off += idLen;
+
+               /*
+                * List of client cipher suites.
+                */
+               if ((off + 2) > msg.Length) {
+                       throw new SSLException("Invalid ClientHello");
+               }
+               int csLen = IO.Dec16be(msg, off);
+               off += 2;
+               if ((off + csLen) > msg.Length) {
+                       throw new SSLException("Invalid ClientHello");
+               }
+               List<int> clientSuites = new List<int>();
+               bool seenReneg = false;
+               while (csLen > 0) {
+                       int cs = IO.Dec16be(msg, off);
+                       off += 2;
+                       csLen -= 2;
+                       if (cs == SSL.FALLBACK_SCSV) {
+                               if (ClientVersionMax < VersionMax) {
+                                       throw new SSLException(
+                                               "Undue fallback detected");
+                               }
+                       } else if (cs == SSL.EMPTY_RENEGOTIATION_INFO_SCSV) {
+                               if (FirstHandshakeDone) {
+                                       throw new SSLException(
+                                               "Reneg SCSV in renegotiation");
+                               }
+                               seenReneg = true;
+                       } else {
+                               clientSuites.Add(cs);
+                       }
+               }
+
+               /*
+                * List of compression methods. We only accept method 0
+                * (no compression).
+                */
+               if ((off + 1) > msg.Length) {
+                       throw new SSLException("Invalid ClientHello");
+               }
+               int compLen = msg[off ++];
+               if ((off + compLen) > msg.Length) {
+                       throw new SSLException("Invalid ClientHello");
+               }
+               bool foundUncompressed = false;
+               while (compLen -- > 0) {
+                       if (msg[off ++] == 0x00) {
+                               foundUncompressed = true;
+                       }
+               }
+               if (!foundUncompressed) {
+                       throw new SSLException("No common compression support");
+               }
+
+               /*
+                * Extensions.
+                */
+               ClientHashAndSign = null;
+               ClientCurves = null;
+               if (off < msg.Length) {
+                       if ((off + 2) > msg.Length) {
+                               throw new SSLException("Invalid ClientHello");
+                       }
+                       int tlen = IO.Dec16be(msg, off);
+                       off += 2;
+                       if ((off + tlen) != msg.Length) {
+                               throw new SSLException("Invalid ClientHello");
+                       }
+                       while (off < msg.Length) {
+                               if ((off + 4) > msg.Length) {
+                                       throw new SSLException(
+                                               "Invalid ClientHello");
+                               }
+                               int etype = IO.Dec16be(msg, off);
+                               int elen = IO.Dec16be(msg, off + 2);
+                               off += 4;
+                               if ((off + elen) > msg.Length) {
+                                       throw new SSLException(
+                                               "Invalid ClientHello");
+                               }
+                               switch (etype) {
+
+                               case 0x0000:
+                                       ParseExtSNI(msg, off, elen);
+                                       break;
+
+                               case 0x000D:
+                                       ParseExtSignatures(msg, off, elen);
+                                       break;
+
+                               case 0x000A:
+                                       ParseExtCurves(msg, off, elen);
+                                       break;
+
+                               case 0xFF01:
+                                       ParseExtSecureReneg(msg, off, elen);
+                                       seenReneg = true;
+                                       break;
+
+                               // Max Frag Length
+                               // ALPN
+                               // FIXME
+                               }
+
+                               off += elen;
+                       }
+               }
+
+               /*
+                * If we are renegotiating and we did not see the
+                * Secure Renegotiation extension, then this is an error.
+                */
+               if (FirstHandshakeDone && !seenReneg) {
+                       throw new SSLException(
+                               "Missing Secure Renegotiation extension");
+               }
+
+               /*
+                * Use prescribed default values for supported algorithms
+                * and curves, when not otherwise advertised by the client.
+                */
+               if (ClientCurves == null) {
+                       ClientCurves = new List<int>();
+                       foreach (int cs in clientSuites) {
+                               if (SSL.IsECDH(cs) || SSL.IsECDHE(cs)) {
+                                       ClientCurves.Add(SSL.NIST_P256);
+                                       break;
+                               }
+                       }
+               }
+               if (ClientHashAndSign == null) {
+                       bool withRSA = false;
+                       bool withECDSA = false;
+                       foreach (int cs in clientSuites) {
+                               if (SSL.IsRSA(cs)
+                                       || SSL.IsECDH_RSA(cs)
+                                       || SSL.IsECDHE_RSA(cs))
+                               {
+                                       withRSA = true;
+                               }
+                               if (SSL.IsECDH_ECDSA(cs)
+                                       || SSL.IsECDHE_ECDSA(cs))
+                               {
+                                       withECDSA = true;
+                               }
+                       }
+                       ClientHashAndSign = new List<int>();
+                       if (withRSA) {
+                               ClientHashAndSign.Add(SSL.RSA_SHA1);
+                       }
+                       if (withECDSA) {
+                               ClientHashAndSign.Add(SSL.ECDSA_SHA1);
+                       }
+               }
+
+               /*
+                * Filter curves and algorithms with regards to our own
+                * configuration.
+                */
+               CommonCurves = FilterList(ClientCurves,
+                       SupportedCurves, EnforceServerOrder);
+               ClientHashAndSign = FilterList(ClientHashAndSign,
+                       SupportedHashAndSign, EnforceServerOrder);
+
+               /*
+                * Selected protocol version (can be overridden by
+                * resumption).
+                */
+               Version = Math.Min(ClientVersionMax, VersionMax);
+
+               /*
+                * Recompute list of acceptable cipher suites. We keep
+                * only suites which are common to the client and server,
+                * with some extra filters.
+                *
+                * Note that when using static ECDH, it is up to the
+                * policy callback to determine whether the curves match
+                * the contents of the certificate.
+                *
+                * We also build a list of common suites for session
+                * resumption: this one may include suites whose
+                * asymmetric crypto is not supported, because session
+                * resumption uses only symmetric crypto.
+                */
+               CommonCipherSuites = new List<int>();
+               List<int> commonSuitesResume = new List<int>();
+               bool canTLS12 = Version >= SSL.TLS12;
+               bool canSignRSA;
+               bool canSignECDSA;
+               if (Version >= SSL.TLS12) {
+                       canSignRSA = false;
+                       canSignECDSA = false;
+                       foreach (int alg in ClientHashAndSign) {
+                               int sa = alg & 0xFF;
+                               switch (sa) {
+                               case SSL.RSA:    canSignRSA = true;    break;
+                               case SSL.ECDSA:  canSignECDSA = true;  break;
+                               }
+                       }
+               } else {
+                       /*
+                        * For pre-1.2, the hash-and-sign configuration does
+                        * not matter, only the cipher suites themselves. So
+                        * we claim support of both RSA and ECDSA signatures
+                        * to avoid trimming the list too much.
+                        */
+                       canSignRSA = true;
+                       canSignECDSA = true;
+               }
+               bool canECDHE = CommonCurves.Count > 0;
+
+               foreach (int cs in clientSuites) {
+                       if (!canTLS12 && SSL.IsTLS12(cs)) {
+                               continue;
+                       }
+                       commonSuitesResume.Add(cs);
+                       if (!canECDHE && SSL.IsECDHE(cs)) {
+                               continue;
+                       }
+                       if (!canSignRSA && SSL.IsECDHE_RSA(cs)) {
+                               continue;
+                       }
+                       if (!canSignECDSA && SSL.IsECDHE_ECDSA(cs)) {
+                               continue;
+                       }
+                       CommonCipherSuites.Add(cs);
+               }
+               CommonCipherSuites = FilterList(CommonCipherSuites,
+                       SupportedCipherSuites, EnforceServerOrder);
+               commonSuitesResume = FilterList(commonSuitesResume,
+                       SupportedCipherSuites, EnforceServerOrder);
+
+               /*
+                * If resuming, then use the remembered session parameters,
+                * but only if they are compatible with what the client
+                * sent AND what we currently support.
+                */
+               SSLSessionParameters sp = null;
+               if (idLen > 0 && !NoResume && SessionCache != null) {
+                       sp = SessionCache.Retrieve(
+                               clientSessionID, ServerName);
+                       if (sp != null && sp.ServerName != null
+                               && ServerName != null)
+                       {
+                               /*
+                                * When resuming a session, if there is
+                                * an explicit name sent by the client,
+                                * and the cached parameters also include
+                                * an explicit name, then both names
+                                * shall match.
+                                */
+                               string s1 = sp.ServerName.ToLowerInvariant();
+                               string s2 = ServerName.ToLowerInvariant();
+                               if (s1 != s2) {
+                                       sp = null;
+                               }
+                       }
+               }
+               if (sp != null) {
+                       bool resumeOK = true;
+                       if (sp.Version < VersionMin
+                               || sp.Version > VersionMax
+                               || sp.Version > ClientVersionMax)
+                       {
+                               resumeOK = false;
+                       }
+                       if (!commonSuitesResume.Contains(sp.CipherSuite)) {
+                               resumeOK = false;
+                       }
+
+                       if (resumeOK) {
+                               /*
+                                * Session resumption is acceptable.
+                                */
+                               resume = true;
+                               sessionID = clientSessionID;
+                               Version = sp.Version;
+                               CipherSuite = sp.CipherSuite;
+                               sessionID = clientSessionID;
+                               SetMasterSecret(sp.MasterSecret);
+                               return true;
+                       }
+               }
+
+               /*
+                * Not resuming. Let's select parameters.
+                * Protocol version was already set.
+                */
+               if (CommonCipherSuites.Count == 0) {
+                       throw new SSLException("No common cipher suite");
+               }
+               serverChoices = ServerPolicy.Apply(this);
+               CipherSuite = serverChoices.GetCipherSuite();
+
+               /*
+                * We create a new session ID, even if we don't have a
+                * session cache, because the session parameters could
+                * be extracted manually by the application.
+                */
+               sessionID = new byte[32];
+               RNG.GetBytes(sessionID);
+
+               return true;
+       }
+
+       void ParseExtSNI(byte[] buf, int off, int len)
+       {
+               if (len < 2) {
+                       throw new SSLException("Invalid SNI extension");
+               }
+               int tlen = IO.Dec16be(buf, off);
+               off += 2;
+               if ((tlen + 2) != len) {
+                       throw new SSLException("Invalid SNI extension");
+               }
+               int lim = off + tlen;
+               bool found = false;
+               while (off < lim) {
+                       if ((off + 3) > lim) {
+                               throw new SSLException("Invalid SNI extension");
+                       }
+                       int ntype = buf[off ++];
+                       int nlen = IO.Dec16be(buf, off);
+                       off += 2;
+                       if ((off + nlen) > lim) {
+                               throw new SSLException("Invalid SNI extension");
+                       }
+                       if (ntype == 0) {
+                               /*
+                                * Name type is "host name". There shall be
+                                * only one (at most) in the extension.
+                                */
+                               if (found) {
+                                       throw new SSLException("Several host"
+                                               + " names in SNI extension");
+                               }
+                               found = true;
+
+                               /*
+                                * Verify that the name contains only
+                                * printable non-space ASCII, and normalise
+                                * it to lowercase.
+                                */
+                               char[] tc = new char[nlen];
+                               for (int i = 0; i < nlen; i ++) {
+                                       int x = buf[off + i];
+                                       if (x <= 32 || x >= 126) {
+                                               throw new SSLException(
+                                                       "Invalid SNI hostname");
+                                       }
+                                       if (x >= 'A' && x <= 'Z') {
+                                               x += ('a' - 'A');
+                                       }
+                                       tc[i] = (char)x;
+                               }
+                               ServerName = new string(tc);
+                       }
+                       off += nlen;
+               }
+       }
+
+       void ParseExtSignatures(byte[] buf, int off, int len)
+       {
+               if (len < 2) {
+                       throw new SSLException("Invalid signatures extension");
+               }
+               int tlen = IO.Dec16be(buf, off);
+               off += 2;
+               if (len != (tlen + 2)) {
+                       throw new SSLException("Invalid signatures extension");
+               }
+               if ((tlen & 1) != 0) {
+                       throw new SSLException("Invalid signatures extension");
+               }
+               ClientHashAndSign = new List<int>();
+               while (tlen > 0) {
+                       ClientHashAndSign.Add(IO.Dec16be(buf, off));
+                       off += 2;
+                       tlen -= 2;
+               }
+       }
+
+       void ParseExtCurves(byte[] buf, int off, int len)
+       {
+               if (len < 2) {
+                       throw new SSLException("Invalid curves extension");
+               }
+               int tlen = IO.Dec16be(buf, off);
+               off += 2;
+               if (len != (tlen + 2)) {
+                       throw new SSLException("Invalid curves extension");
+               }
+               if ((tlen & 1) != 0) {
+                       throw new SSLException("Invalid curves extension");
+               }
+               ClientCurves = new List<int>();
+               while (tlen > 0) {
+                       ClientCurves.Add(IO.Dec16be(buf, off));
+                       off += 2;
+                       tlen -= 2;
+               }
+       }
+
+       void ParseExtSecureReneg(byte[] buf, int off, int len)
+       {
+               if (len < 1 || len != 1 + buf[off]) {
+                       throw new SSLException(
+                               "Invalid Secure Renegotiation extension");
+               }
+               len --;
+               off ++;
+
+               if (renegSupport == 0) {
+                       /*
+                        * Initial handshake: extension MUST be empty.
+                        */
+                       if (len != 0) {
+                               throw new SSLException(
+                                       "Non-empty Secure Renegotation"
+                                       + " on initial handshake");
+                       }
+                       renegSupport = 1;
+               } else {
+                       /*
+                        * Renegotiation: extension MUST contain the
+                        * saved client Finished message.
+                        */
+                       if (len != 12) {
+                               throw new SSLException(
+                                       "Wrong Secure Renegotiation value");
+                       }
+                       int z = 0;
+                       for (int i = 0; i < 12; i ++) {
+                               z |= savedClientFinished[i] ^ buf[off + i];
+                       }
+                       if (z != 0) {
+                               throw new SSLException(
+                                       "Wrong Secure Renegotiation value");
+                       }
+               }
+       }
+
+       void SendServerHello()
+       {
+               MemoryStream ms = StartHandshakeMessage(SSL.SERVER_HELLO);
+
+               // Protocol version
+               IO.Write16(ms, Version);
+
+               // Server random
+               ms.Write(serverRandom, 0, serverRandom.Length);
+
+               // Session ID
+               ms.WriteByte((byte)sessionID.Length);
+               ms.Write(sessionID, 0, sessionID.Length);
+
+               // Cipher suite
+               IO.Write16(ms, CipherSuite);
+
+               // Compression
+               ms.WriteByte(0x00);
+
+               // Extensions
+               MemoryStream chExt = new MemoryStream();
+
+               // Secure renegotiation
+               if (!GetQuirkBool("noSecureReneg")) {
+                       byte[] exv = null;
+                       if (renegSupport > 0) {
+                               if (FirstHandshakeDone) {
+                                       exv = new byte[24];
+                                       Array.Copy(savedClientFinished, 0,
+                                               exv, 0, 12);
+                                       Array.Copy(savedServerFinished, 0,
+                                               exv, 12, 12);
+                               } else {
+                                       exv = new byte[0];
+                               }
+                       }
+                       if (GetQuirkBool("forceEmptySecureReneg")) {
+                               exv = new byte[0];
+                       } else if (GetQuirkBool("forceNonEmptySecureReneg")) {
+                               exv = new byte[24];
+                       } else if (GetQuirkBool("alterNonEmptySecureReneg")) {
+                               if (exv.Length > 0) {
+                                       exv[exv.Length - 1] ^= 0x01;
+                               }
+                       } else if (GetQuirkBool("oversizedSecureReneg")) {
+                               exv = new byte[255];
+                       }
+
+                       if (exv != null) {
+                               IO.Write16(chExt, 0xFF01);
+                               IO.Write16(chExt, exv.Length + 1);
+                               chExt.WriteByte((byte)exv.Length);
+                               chExt.Write(exv, 0, exv.Length);
+                       }
+               }
+
+               // Extra extension with random contents.
+               int extraExt = GetQuirkInt("sendExtraExtension", -1);
+               if (extraExt >= 0) {
+                       byte[] exv = new byte[extraExt >> 16];
+                       RNG.GetBytes(exv);
+                       IO.Write16(chExt, extraExt & 0xFFFF);
+                       IO.Write16(chExt, exv.Length);
+                       chExt.Write(exv, 0, exv.Length);
+               }
+
+               // Max Fragment Length
+               // ALPN
+               // FIXME
+
+               byte[] encExt = chExt.ToArray();
+               if (encExt.Length > 0) {
+                       if (encExt.Length > 65535) {
+                               throw new SSLException("Oversized extensions");
+                       }
+                       IO.Write16(ms, encExt.Length);
+                       ms.Write(encExt, 0, encExt.Length);
+               }
+
+               EndHandshakeMessage(ms);
+       }
+
+       void SendCertificate()
+       {
+               MemoryStream ms = StartHandshakeMessage(SSL.CERTIFICATE);
+               byte[][] chain = serverChoices.GetCertificateChain();
+               int tlen = 0;
+               foreach (byte[] ec in chain) {
+                       tlen += 3 + ec.Length;
+               }
+               if (tlen > 0xFFFFFC) {
+                       throw new SSLException("Oversized certificate chain");
+               }
+               IO.Write24(ms, tlen);
+               foreach (byte[] ec in chain) {
+                       IO.Write24(ms, ec.Length);
+                       ms.Write(ec, 0, ec.Length);
+               }
+               EndHandshakeMessage(ms);
+       }
+
+       void SendServerKeyExchange()
+       {
+               if (CommonCurves.Count == 0) {
+                       /*
+                        * Since we filter cipher suites when parsing the
+                        * ClientHello, this situation may happen only if
+                        * the IServerPolicy callback goofed up.
+                        */
+                       throw new SSLException("No curve for ECDHE");
+               }
+               int curveID = CommonCurves[0];
+               ecdheCurve = SSL.GetCurveByID(curveID);
+
+               /*
+                * Generate our ephemeral ECDH secret and the point to
+                * send to the peer.
+                */
+               ecdheSecret = ecdheCurve.MakeRandomSecret();
+               byte[] P = ecdheCurve.GetGenerator(false);
+               ecdheCurve.Mul(P, ecdheSecret, P, false);
+
+               /*
+                * Generate to-be-signed:
+                *   clientRandom   32 bytes
+                *   serverRandom   32 bytes
+                *   0x03           curve is a "named curve"
+                *   id             curve identifier (two bytes)
+                *   point          public point (one-byte length + value)
+                */
+               byte[] tbs = new byte[64 + 4 + P.Length];
+               Array.Copy(clientRandom, 0, tbs, 0, 32);
+               Array.Copy(serverRandom, 0, tbs, 32, 32);
+               tbs[64] = 0x03;
+               IO.Enc16be(curveID, tbs, 65);
+               tbs[67] = (byte)P.Length;
+               Array.Copy(P, 0, tbs, 68, P.Length);
+
+               /*
+                * Obtain server signature.
+                */
+               int hashAlgo, sigAlgo;
+               byte[] sig = serverChoices.DoSign(tbs,
+                       out hashAlgo, out sigAlgo);
+
+               /*
+                * Encode message.
+                */
+               MemoryStream ms = StartHandshakeMessage(
+                       SSL.SERVER_KEY_EXCHANGE);
+               ms.Write(tbs, 64, tbs.Length - 64);
+               if (Version >= SSL.TLS12) {
+                       ms.WriteByte((byte)hashAlgo);
+                       ms.WriteByte((byte)sigAlgo);
+               }
+               IO.Write16(ms, sig.Length);
+               ms.Write(sig, 0, sig.Length);
+               EndHandshakeMessage(ms);
+       }
+
+       void SendServerHelloDone()
+       {
+               MemoryStream ms = StartHandshakeMessage(SSL.SERVER_HELLO_DONE);
+               EndHandshakeMessage(ms);
+       }
+
+       void ParseClientKeyExchange()
+       {
+               byte[] msg = ReadHandshakeMessageExpected(
+                       SSL.CLIENT_KEY_EXCHANGE);
+               byte[] pms;
+               if (SSL.IsECDHE(CipherSuite)) {
+                       /*
+                        * Expecting a curve point; we are doing the
+                        * ECDH ourselves.
+                        */
+                       if (msg.Length < 1 || msg.Length != 1 + msg[0]) {
+                               throw new SSLException(
+                                       "Invalid ClientKeyExchange");
+                       }
+                       byte[] P = new byte[msg.Length - 1];
+                       byte[] D = new byte[ecdheCurve.EncodedLength];
+                       Array.Copy(msg, 1, P, 0, P.Length);
+                       if (ecdheCurve.Mul(P, ecdheSecret, D, false) == 0) {
+                               throw new SSLException(
+                                       "Invalid ClientKeyExchange");
+                       }
+                       int xlen;
+                       int xoff = ecdheCurve.GetXoff(out xlen);
+                       pms = new byte[xlen];
+                       Array.Copy(D, xoff, pms, 0, xlen);
+
+                       /*
+                        * Memory wiping is out of scope for this library,
+                        * and is unreliable anyway in the presence of
+                        * a moving garbage collector. So we just unlink
+                        * the secret array.
+                        */
+                       ecdheSecret = null;
+               } else {
+                       /*
+                        * RSA or static ECDH. The crypto operation is done
+                        * by the relevant callback.
+                        */
+                       if (msg.Length < 2) {
+                               throw new SSLException(
+                                       "Invalid ClientKeyExchange");
+                       }
+                       int off, len;
+                       if (SSL.IsRSA(CipherSuite)) {
+                               len = IO.Dec16be(msg, 0);
+                               off = 2;
+                       } else if (SSL.IsECDH(CipherSuite)) {
+                               len = msg[0];
+                               off = 1;
+                       } else {
+                               throw new Exception("NYI");
+                       }
+                       if (msg.Length != off + len) {
+                               throw new SSLException(
+                                       "Invalid ClientKeyExchange");
+                       }
+                       byte[] cke = new byte[len];
+                       Array.Copy(msg, off, cke, 0, len);
+                       pms = serverChoices.DoKeyExchange(cke);
+               }
+
+               ComputeMaster(pms);
+       }
+
+       internal override void ProcessExtraHandshake()
+       {
+               /*
+                * If Secure Renegotiation is supported, then we accept
+                * to do a new handshake.
+                */
+               if (renegSupport > 0) {
+                       DoHandshake();
+                       return;
+               }
+
+               /*
+                * We must read and discard an incoming ClientHello,
+                * then politely refuse.
+                */
+               ReadHandshakeMessageExpected(SSL.CLIENT_HELLO);
+               SendWarning(SSL.NO_RENEGOTIATION);
+               SetAppData();
+       }
+
+       internal override void PrepareRenegotiate()
+       {
+               MemoryStream ms = StartHandshakeMessage(SSL.HELLO_REQUEST);
+               EndHandshakeMessage(ms);
+               FlushSub();
+       }
+
+       /*
+        * Compute the intersection of two lists of integers (the second
+        * list is provided as an array). The intersection is returned
+        * as a new List<int> instance. If enforceV2 is true, then the
+        * order of items in the returned list will be that of v2; otherwise,
+        * it will be that of v1. Duplicates are removed.
+        */
+       static List<int> FilterList(List<int> v1, int[] v2, bool enforceV2)
+       {
+               List<int> r = new List<int>();
+               if (enforceV2) {
+                       foreach (int x in v2) {
+                               if (v1.Contains(x) && !r.Contains(x)) {
+                                       r.Add(x);
+                               }
+                       }
+               } else {
+                       foreach (int x in v1) {
+                               foreach (int y in v2) {
+                                       if (x == y) {
+                                               if (!r.Contains(x)) {
+                                                       r.Add(x);
+                                               }
+                                               break;
+                                       }
+                               }
+                       }
+               }
+               return r;
+       }
+}
+
+}
diff --git a/SSLTLS/SSLServerPolicyBasic.cs b/SSLTLS/SSLServerPolicyBasic.cs
new file mode 100644 (file)
index 0000000..d4bf1db
--- /dev/null
@@ -0,0 +1,358 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+using Crypto;
+
+namespace SSLTLS {
+
+/*
+ * Basic implementation of IServerPolicy: it uses a single certificate
+ * chain and a software private key.
+ */
+
+public class SSLServerPolicyBasic : IServerPolicy {
+
+       byte[][] chain;
+       IPrivateKey skey;
+       bool canSign, canEncrypt;
+
+       /*
+        * Create the policy instance, with the provided certificate chain,
+        * private key, and allowed key usages.
+        */
+       public SSLServerPolicyBasic(byte[][] chain,
+               IPrivateKey skey, KeyUsage usage)
+       {
+               this.chain = chain;
+               this.skey = skey;
+               canEncrypt = false;
+               canSign = false;
+               switch (usage) {
+               case KeyUsage.EncryptOnly:
+                       canEncrypt = true;
+                       break;
+               case KeyUsage.SignOnly:
+                       canSign = true;
+                       break;
+               case KeyUsage.EncryptAndSign:
+                       canEncrypt = true;
+                       canSign = true;
+                       break;
+               }
+       }
+
+       public IServerChoices Apply(SSLServer server)
+       {
+               /*
+                * Conditions for selecting a cipher suite:
+                *
+                *   RSA           canEncrypt, key is RSA
+                *
+                *   ECDH          canEncrypt, key is EC, curve supported
+                *
+                *   ECDHE_RSA     canSign, key is RSA, have hash+RSA algo
+                *
+                *   ECDHE_ECDSA   canSign, key is EC, have hash+ECDSA algo,
+                *                 curve supported
+                *
+                * The engine already filtered things, so we know that:
+                *
+                *  - if an ECDHE suite is present, then there is a common
+                *    supported curve;
+                *
+                *  - if an ECDHE_RSA suite is present, then there is a
+                *    common hash+RSA algorithm;
+                *
+                *  - if an ECDHE_ECDSA suite is present, then there is a
+                *    common hash+ECDSA algorithm.
+                *
+                * We must still walk the list of algorithm to determine the
+                * proper hash to use for signatures; we also need to check
+                * that our EC curve is supported by the client.
+                */
+               int curveID = -1;
+               if (skey is ECPrivateKey) {
+                       curveID = SSL.CurveToID(((ECPrivateKey)skey).Curve);
+               }
+               bool canRSA = canEncrypt && (skey is RSAPrivateKey);
+               bool canECDH = canEncrypt && (skey is ECPrivateKey)
+                       && server.ClientCurves.Contains(curveID);
+               bool canECDHE_RSA = canSign && (skey is RSAPrivateKey);
+               bool canECDHE_ECDSA = canSign && (skey is ECPrivateKey);
+
+               foreach (int cs in server.CommonCipherSuites) {
+                       if (SSL.IsRSA(cs)) {
+                               if (!canRSA) {
+                                       continue;
+                               }
+                               return new ChoicesRSA(server.ClientVersionMax,
+                                       cs, chain, skey as RSAPrivateKey);
+                       } else if (SSL.IsECDH(cs)) {
+                               if (!canECDH) {
+                                       continue;
+                               }
+                               return new ChoicesECDH(cs, chain,
+                                       skey as ECPrivateKey);
+                       } else if (SSL.IsECDHE_RSA(cs)) {
+                               if (!canECDHE_RSA) {
+                                       continue;
+                               }
+                               int hashAlgo;
+                               if (server.Version <= SSL.TLS11) {
+                                       hashAlgo = SSL.MD5SHA1;
+                               } else {
+                                       hashAlgo = SelectHash(
+                                               server.ClientHashAndSign,
+                                               SSL.RSA);
+                               }
+                               return new ChoicesSign(cs, chain,
+                                       hashAlgo, skey);
+                       } else if (SSL.IsECDHE_ECDSA(cs)) {
+                               if (!canECDHE_ECDSA) {
+                                       continue;
+                               }
+                               int hashAlgo;
+                               if (server.Version <= SSL.TLS11) {
+                                       hashAlgo = SSL.SHA1;
+                               } else {
+                                       hashAlgo = SelectHash(
+                                               server.ClientHashAndSign,
+                                               SSL.ECDSA);
+                               }
+                               return new ChoicesSign(cs, chain,
+                                       hashAlgo, skey);
+                       }
+               }
+
+               throw new SSLException("No suitable cipher suite");
+       }
+
+       static int SelectHash(List<int> hsl, int sigAlg)
+       {
+               foreach (int x in hsl) {
+                       if ((x & 0xFF) == sigAlg) {
+                               return x >> 8;
+                       }
+               }
+
+               /*
+                * This should never happen, because the offending
+                * cipher suites would have been filtered by the engine.
+                */
+               throw new Exception();
+       }
+}
+
+class ChoicesBase {
+
+       int cipherSuite;
+       byte[][] chain;
+
+       internal ChoicesBase(int cipherSuite, byte[][] chain)
+       {
+               this.cipherSuite = cipherSuite;
+               this.chain = chain;
+       }
+
+       public int GetCipherSuite()
+       {
+               return cipherSuite;
+       }
+
+       public byte[][] GetCertificateChain()
+       {
+               return chain;
+       }
+
+       public virtual byte[] DoKeyExchange(byte[] cke)
+       {
+               throw new Exception();
+       }
+
+       public virtual byte[] DoSign(byte[] ske,
+               out int hashAlgo, out int sigAlgo)
+       {
+               throw new Exception();
+       }
+}
+
+class ChoicesRSA : ChoicesBase, IServerChoices {
+
+       int clientVersionMax;
+       RSAPrivateKey rk;
+
+       internal ChoicesRSA(int clientVersionMax, int cipherSuite,
+               byte[][] chain, RSAPrivateKey rk)
+               : base(cipherSuite, chain)
+       {
+               this.clientVersionMax = clientVersionMax;
+               this.rk = rk;
+       }
+
+       public override byte[] DoKeyExchange(byte[] cke)
+       {
+               if (cke.Length < 59) {
+                       throw new CryptoException(
+                               "Invalid ClientKeyExchange (too short)");
+               }
+               RSA.DoPrivate(rk, cke);
+
+               /*
+                * Constant-time check for PKCS#1 v1.5 padding. z is set
+                * to -1 if the padding is correct, 0 otherwise. We also
+                * check the two first PMS byte (they should be equal to
+                * the maximum protocol version announced by the client
+                * in its ClientHello).
+                */
+               int z = 0;
+               z |= cke[0];
+               z |= cke[1] ^ 0x02;
+               for (int i = 2; i < cke.Length - 49; i ++) {
+                       int y = cke[i];
+                       z |= ~((y | -y) >> 31);
+               }
+               z |= cke[cke.Length - 49];
+               z |= cke[cke.Length - 48] ^ (clientVersionMax >> 8);
+               z |= cke[cke.Length - 47] ^ (clientVersionMax & 0xFF);
+               z = ~((z | -z) >> 31);
+
+               /*
+                * Get a random premaster, then overwrite it with the
+                * decrypted value, but only if the padding was correct.
+                */
+               byte[] pms = new byte[48];
+               RNG.GetBytes(pms);
+               for (int i = 0; i < 48; i ++) {
+                       int x = pms[i];
+                       int y = cke[cke.Length - 48 + i];
+                       pms[i] = (byte)(x ^ (z & (x ^ y)));
+               }
+
+               return pms;
+       }
+}
+
+class ChoicesECDH : ChoicesBase, IServerChoices {
+
+       ECPrivateKey ek;
+
+       internal ChoicesECDH(int cipherSuite, byte[][] chain, ECPrivateKey ek)
+               : base(cipherSuite, chain)
+       {
+               this.ek = ek;
+       }
+
+       public override byte[] DoKeyExchange(byte[] cke)
+       {
+               ECCurve curve = ek.Curve;
+               byte[] tmp = new byte[curve.EncodedLength];
+               if (curve.Mul(cke, ek.X, tmp, false) == 0) {
+                       throw new SSLException(
+                               "Invalid ClientKeyExchange EC point value");
+               }
+               int xlen;
+               int xoff = curve.GetXoff(out xlen);
+               byte[] pms = new byte[xlen];
+               Array.Copy(tmp, xoff, pms, 0, xlen);
+               return pms;
+       }
+}
+
+class ChoicesSign : ChoicesBase, IServerChoices {
+
+       int hashAlgo;
+       IPrivateKey skey;
+
+       internal ChoicesSign(int cipherSuite, byte[][] chain,
+               int hashAlgo, IPrivateKey skey)
+               : base(cipherSuite, chain)
+       {
+               this.hashAlgo = hashAlgo;
+               this.skey = skey;
+       }
+
+       public override byte[] DoSign(byte[] ske,
+               out int hashAlgo, out int signAlgo)
+       {
+               hashAlgo = this.hashAlgo;
+               byte[] hv = Hash(hashAlgo, ske);
+               if (skey is RSAPrivateKey) {
+                       RSAPrivateKey rk = skey as RSAPrivateKey;
+                       signAlgo = SSL.RSA;
+                       byte[] head;
+                       switch (hashAlgo) {
+                       case SSL.MD5SHA1: head = null; break;
+                       case SSL.SHA1:    head = RSA.PKCS1_SHA1; break;
+                       case SSL.SHA224:  head = RSA.PKCS1_SHA224; break;
+                       case SSL.SHA256:  head = RSA.PKCS1_SHA256; break;
+                       case SSL.SHA384:  head = RSA.PKCS1_SHA384; break;
+                       case SSL.SHA512:  head = RSA.PKCS1_SHA512; break;
+                       default:
+                               throw new Exception();
+                       }
+                       return RSA.Sign(rk, head, hv);
+               } else if (skey is ECPrivateKey) {
+                       ECPrivateKey ek = skey as ECPrivateKey;
+                       signAlgo = SSL.ECDSA;
+                       return ECDSA.Sign(ek, null, hv);
+               } else {
+                       throw new Exception("NYI");
+               }
+       }
+
+       static byte[] Hash(int hashAlgo, byte[] data)
+       {
+               switch (hashAlgo) {
+               case SSL.MD5SHA1:
+                       byte[] hv = new byte[36];
+                       MD5 md5 = new MD5();
+                       SHA1 sha1 = new SHA1();
+                       md5.Update(data);
+                       md5.DoFinal(hv, 0);
+                       sha1.Update(data);
+                       sha1.DoFinal(hv, 16);
+                       return hv;
+               case SSL.MD5:
+                       return new MD5().Hash(data);
+               case SSL.SHA1:
+                       return new SHA1().Hash(data);
+               case SSL.SHA224:
+                       return new SHA224().Hash(data);
+               case SSL.SHA256:
+                       return new SHA256().Hash(data);
+               case SSL.SHA384:
+                       return new SHA384().Hash(data);
+               case SSL.SHA512:
+                       return new SHA512().Hash(data);
+               default:
+                       throw new Exception("NYI");
+               }
+       }
+}
+
+}
diff --git a/SSLTLS/SSLSessionCacheLRU.cs b/SSLTLS/SSLSessionCacheLRU.cs
new file mode 100644 (file)
index 0000000..916638d
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace SSLTLS {
+
+/*
+ * A basic implementation of an SSL session cache. It stores up to a
+ * predetermined number of sessions, and uses a least-recently-used
+ * eviction policy. Sessions are kept in RAM.
+ *
+ * Instances use locking to be thread-safe, so that multiple SSL server
+ * engines managed in different threads may share the same cache (though
+ * each engine, individually, is not thread-safe).
+ */
+
+public class SSLSessionCacheLRU : ISessionCache {
+
+       /*
+        * TODO: use a doubly-linked list for LRU policy. Right now,
+        * values are indexed in an array, and eviction/moving implies
+        * shifting many pointers in that array. This is fine for small
+        * caches, up to a few thousands of entries.
+        */
+
+       object mutex;
+       IDictionary<string, int> spx;
+       SSLSessionParameters[] data;
+       int count, maxCount;
+
+       public SSLSessionCacheLRU(int maxCount)
+       {
+               spx = new Dictionary<string, int>();
+               data = new SSLSessionParameters[maxCount];
+               count = 0;
+               this.maxCount = maxCount;
+               mutex = new object();
+       }
+
+       /* see ISessionCache */
+       public SSLSessionParameters Retrieve(byte[] id, string serverName)
+       {
+               lock (mutex) {
+                       int x;
+                       if (!spx.TryGetValue(IDToString(id), out x)) {
+                               return null;
+                       }
+                       SSLSessionParameters sp = data[x];
+                       if ((x + 1) < count) {
+                               Array.Copy(data, x + 1, 
+                                       data, x, count - x - 1);
+                               data[count - 1] = sp;
+                       }
+                       return sp;
+               }
+       }
+
+       /* see ISessionCache */
+       public void Store(SSLSessionParameters sp)
+       {
+               lock (mutex) {
+                       string ids = IDToString(sp.SessionID);
+                       int x;
+                       if (spx.TryGetValue(ids, out x)) {
+                               if ((x + 1) < count) {
+                                       Array.Copy(data, x + 1,
+                                               data, x, count - x - 1);
+                               }
+                               spx[ids] = count - 1;
+                               data[count - 1] = sp;
+                               return;
+                       }
+                       if (count == maxCount) {
+                               SSLSessionParameters esp = data[0];
+                               Array.Copy(data, 1, data, 0, count - 1);
+                               count --;
+                               spx.Remove(IDToString(esp.SessionID));
+                       }
+                       spx[ids] = count;
+                       data[count] = sp;
+                       count ++;
+               }
+       }
+
+       static string IDToString(byte[] id)
+       {
+               StringBuilder sb = new StringBuilder();
+               foreach (byte b in id) {
+                       sb.AppendFormat("{0:x2}", b);
+               }
+               return sb.ToString();
+       }
+}
+
+}
diff --git a/SSLTLS/SSLSessionParameters.cs b/SSLTLS/SSLSessionParameters.cs
new file mode 100644 (file)
index 0000000..a1e0c03
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+namespace SSLTLS {
+
+/*
+ * An SSLSessionParameters instance contains the "security parameters"
+ * for a SSL session. Instances are obtained from an open instance
+ * (after the handshake) and can be used to initialise new instances
+ * so as to attempt a session resumption.
+ */
+
+public class SSLSessionParameters {
+
+       /*
+        * Session ID (at most 32 bytes).
+        */
+       public byte[] SessionID {
+               get; set;
+       }
+
+       /*
+        * Protocol version.
+        */
+       public int Version {
+               get; set;
+       }
+
+       /*
+        * Used cipher suite.
+        */
+       public int CipherSuite {
+               get; set;
+       }
+
+       /*
+        * Server name attached to this session; it may be null.
+        */
+       public string ServerName {
+               get; set;
+       }
+
+       /*
+        * Negotiated master secret.
+        */
+       public byte[] MasterSecret {
+               get; set;
+       }
+
+       /*
+        * Create a new instance. The provided sessionID and masterSecret
+        * arrays are internally copied into new instances.
+        */
+       public SSLSessionParameters(byte[] sessionID, int version,
+               int cipherSuite, string serverName, byte[] masterSecret)
+       {
+               SessionID = IO.CopyBlob(sessionID);
+               Version = version;
+               CipherSuite = cipherSuite;
+               ServerName = serverName;
+               MasterSecret = IO.CopyBlob(masterSecret);
+       }
+}
+
+}
diff --git a/Tests/Poly1305Ref.cs b/Tests/Poly1305Ref.cs
new file mode 100644 (file)
index 0000000..a1d0420
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+using Crypto;
+
+/*
+ * This is a "reference" implementation of Poly1305 that uses the
+ * generic ZInt code for computations. It is not constant-time, and
+ * it is very slow; it is meant to test other implementations.
+ *
+ * API is identical to the Poly1305 class.
+ */
+
+public class Poly1305Ref {
+
+       public ChaCha20 ChaCha {
+               get; set;
+       }
+
+       public Poly1305Ref()
+       {
+       }
+
+       static ZInt p = ((ZInt)1 << 130) - (ZInt)5;
+       static ZInt rmask = ((ZInt)1 << 124) - (ZInt)1
+               - ((ZInt)15 << 28) - ((ZInt)15 << 60) - ((ZInt)15 << 92)
+               - ((ZInt)3 << 32) - ((ZInt)3 << 64) - ((ZInt)3 << 96);
+
+       public void Run(byte[] iv,
+               byte[] data, int off, int len,
+               byte[] aad, int offAAD, int lenAAD,
+               byte[] tag, bool encrypt)
+       {
+               byte[] pkey = new byte[32];
+               ChaCha.Run(iv, 0, pkey);
+               if (encrypt) {
+                       ChaCha.Run(iv, 1, data, off, len);
+               }
+
+               ByteSwap(pkey, 0, 16);
+               ZInt r = ZInt.DecodeUnsignedBE(pkey, 0, 16);
+               r &= rmask;
+               ZInt a = (ZInt)0;
+
+               a = RunInner(a, r, aad, offAAD, lenAAD);
+               a = RunInner(a, r, data, off, len);
+               byte[] foot = new byte[16];
+               foot[ 0] = (byte)lenAAD;
+               foot[ 1] = (byte)(lenAAD >> 8);
+               foot[ 2] = (byte)(lenAAD >> 16);
+               foot[ 3] = (byte)(lenAAD >> 24);
+               foot[ 8] = (byte)len;
+               foot[ 9] = (byte)(len >> 8);
+               foot[10] = (byte)(len >> 16);
+               foot[11] = (byte)(len >> 24);
+               a = RunInner(a, r, foot, 0, 16);
+
+               ByteSwap(pkey, 16, 16);
+               ZInt s = ZInt.DecodeUnsignedBE(pkey, 16, 16);
+               a += s;
+               a.ToBytesLE(tag, 0, 16);
+
+               if (!encrypt) {
+                       ChaCha.Run(iv, 1, data, off, len);
+               }
+       }
+
+       ZInt RunInner(ZInt a, ZInt r, byte[] data, int off, int len)
+       {
+               byte[] tmp = new byte[16];
+               while (len > 0) {
+                       if (len >= 16) {
+                               Array.Copy(data, off, tmp, 0, 16);
+                       } else {
+                               Array.Copy(data, off, tmp, 0, len);
+                               for (int i = len; i < 16; i ++) {
+                                       tmp[i] = 0;
+                               }
+                       }
+                       ByteSwap(tmp, 0, 16);
+                       ZInt v = ZInt.DecodeUnsignedBE(tmp) | ((ZInt)1 << 128);
+                       a = ((a + v) * r) % p;
+                       off += 16;
+                       len -= 16;
+               }
+               return a;
+       }
+
+       static void ByteSwap(byte[] buf, int off, int len)
+       {
+               for (int i = 0; (i + i) < len; i ++) {
+                       byte t = buf[off + i];
+                       buf[off + i] = buf[off + len - 1 - i];
+                       buf[off + len - 1 - i] = t;
+               }
+       }
+}
diff --git a/Tests/TestCrypto.cs b/Tests/TestCrypto.cs
new file mode 100644 (file)
index 0000000..cbcff5f
--- /dev/null
@@ -0,0 +1,3843 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+using Crypto;
+
+public class TestCrypto {
+
+       public static void Main(string[] args)
+       {
+               IDictionary<string, bool> d =
+                       new SortedDictionary<string, bool>(
+                               StringComparer.OrdinalIgnoreCase);
+               foreach (string a in args) {
+                       StringBuilder sb = new StringBuilder();
+                       foreach (char c in a.ToLowerInvariant()) {
+                               if ((c >= 'a' && c <= 'z')
+                                       || (c >= '0' && c <= '9'))
+                               {
+                                       sb.Append(c);
+                               }
+                       }
+                       d[sb.ToString()] = true;
+               }
+               bool all = (d.Count == 0);
+               try {
+                       if (all || d.ContainsKey("md5")) {
+                               TestMD5();
+                       }
+                       if (all || d.ContainsKey("sha1")) {
+                               TestSHA1();
+                       }
+                       if (all || d.ContainsKey("sha224")) {
+                               TestSHA224();
+                       }
+                       if (all || d.ContainsKey("sha256")) {
+                               TestSHA256();
+                       }
+                       if (all || d.ContainsKey("sha384")) {
+                               TestSHA384();
+                       }
+                       if (all || d.ContainsKey("sha512")) {
+                               TestSHA512();
+                       }
+                       if (all || d.ContainsKey("hmac")) {
+                               TestHMAC();
+                       }
+                       if (all || d.ContainsKey("hmacdrbg")) {
+                               TestHMAC_DRBG();
+                       }
+                       if (all || d.ContainsKey("aes")) {
+                               TestAES();
+                       }
+                       if (all || d.ContainsKey("des")) {
+                               TestDES();
+                       }
+                       if (all || d.ContainsKey("chacha20")) {
+                               TestChaCha20();
+                       }
+                       if (all || d.ContainsKey("poly1305")) {
+                               TestPoly1305();
+                       }
+                       if (all || d.ContainsKey("ghash")) {
+                               TestGHASH();
+                       }
+                       if (all || d.ContainsKey("int")) {
+                               TestMath.TestModInt();
+                       }
+                       if (all || d.ContainsKey("rsa")) {
+                               TestRSA();
+                       }
+                       if (all || d.ContainsKey("ec")) {
+                               TestEC.TestECInt();
+                       }
+                       if (all || d.ContainsKey("ecdsa")) {
+                               TestECDSA();
+                       }
+               } catch (Exception e) {
+                       Console.WriteLine(e.ToString());
+                       Environment.Exit(1);
+               }
+       }
+
+       static void TestMD5()
+       {
+               Console.Write("Testing MD5... ");
+               DoKATHash(new MD5(), KAT_MD5);
+               Console.WriteLine("done.");
+       }
+
+       static void TestSHA1()
+       {
+               Console.Write("Testing SHA-1... ");
+               DoKATHash(new SHA1(), KAT_SHA1);
+               DoKATHashLong(new SHA1(), "34aa973cd4c4daa4f61eeb2bdbad27316534016f");
+               Console.WriteLine("done.");
+       }
+
+       static void TestSHA224()
+       {
+               Console.Write("Testing SHA-224... ");
+               DoKATHash(new SHA224(), KAT_SHA224);
+               DoKATHashLong(new SHA224(), "20794655980c91d8bbb4c1ea97618a4bf03f42581948b2ee4ee7ad67");
+               Console.WriteLine("done.");
+       }
+
+       static void TestSHA256()
+       {
+               Console.Write("Testing SHA-256... ");
+               DoKATHash(new SHA256(), KAT_SHA256);
+               DoKATHashLong(new SHA256(), "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0");
+               Console.WriteLine("done.");
+       }
+
+       static void TestSHA384()
+       {
+               Console.Write("Testing SHA-384... ");
+               DoKATHash(new SHA384(), KAT_SHA384);
+               DoKATHashLong(new SHA384(), "9d0e1809716474cb086e834e310a4a1ced149e9c00f248527972cec5704c2a5b07b8b3dc38ecc4ebae97ddd87f3d8985");
+               Console.WriteLine("done.");
+       }
+
+       static void TestSHA512()
+       {
+               Console.Write("Testing SHA-512... ");
+               DoKATHash(new SHA512(), KAT_SHA512);
+               DoKATHashLong(new SHA512(), "e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b");
+               Console.WriteLine("done.");
+       }
+
+       static void DoKATHash(IDigest h, string[] katTab)
+       {
+               for (int i = 0; i < katTab.Length; i += 2) {
+                       DoKATHash(h,
+                               Encoding.UTF8.GetBytes(katTab[i]),
+                               ToBin(katTab[i + 1]));
+               }
+       }
+
+       static void TestHMAC()
+       {
+               Console.Write("Testing HMAC... ");
+
+               DoKATHMAC(new HMAC(new MD5()),
+                       ToBin("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
+                       Encoding.UTF8.GetBytes("Hi There"),
+                       ToBin("9294727a3638bb1c13f48ef8158bfc9d"));
+               DoKATHMAC(new HMAC(new MD5()),
+                       Encoding.UTF8.GetBytes("Jefe"),
+                       Encoding.UTF8.GetBytes("what do ya want for nothing?"),
+                       ToBin("750c783e6ab0b503eaa86e310a5db738"));
+               DoKATHMAC(new HMAC(new MD5()),
+                       ToBin("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
+                       ToBin("DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"),
+                       ToBin("56be34521d144c88dbb8c733f0e8b3f6"));
+               DoKATHMAC(new HMAC(new MD5()),
+                       ToBin("0102030405060708090a0b0c0d0e0f10111213141516171819"),
+                       ToBin("CDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCD"),
+                       ToBin("697eaf0aca3a3aea3a75164746ffaa79"));
+               DoKATHMAC(new HMAC(new MD5()),
+                       ToBin("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c"),
+                       Encoding.UTF8.GetBytes("Test With Truncation"),
+                       ToBin("56461ef2342edc00f9bab995690efd4c"));
+               DoKATHMAC(new HMAC(new MD5()),
+                       ToBin("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
+                       Encoding.UTF8.GetBytes("Test Using Larger Than Block-Size Key - Hash Key First"),
+                       ToBin("6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd"));
+               DoKATHMAC(new HMAC(new MD5()),
+                       ToBin("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
+                       Encoding.UTF8.GetBytes("Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data"),
+                       ToBin("6f630fad67cda0ee1fb1f562db3aa53e"));
+
+               DoKATHMAC(new HMAC(new SHA1()),
+                       ToBin("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
+                       Encoding.UTF8.GetBytes("Hi There"),
+                       ToBin("b617318655057264e28bc0b6fb378c8ef146be00"));
+               DoKATHMAC(new HMAC(new SHA1()),
+                       Encoding.UTF8.GetBytes("Jefe"),
+                       Encoding.UTF8.GetBytes("what do ya want for nothing?"),
+                       ToBin("effcdf6ae5eb2fa2d27416d5f184df9c259a7c79"));
+               DoKATHMAC(new HMAC(new SHA1()),
+                       ToBin("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
+                       ToBin("DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"),
+                       ToBin("125d7342b9ac11cd91a39af48aa17b4f63f175d3"));
+               DoKATHMAC(new HMAC(new SHA1()),
+                       ToBin("0102030405060708090a0b0c0d0e0f10111213141516171819"),
+                       ToBin("CDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCD"),
+                       ToBin("4c9007f4026250c6bc8414f9bf50c86c2d7235da"));
+               DoKATHMAC(new HMAC(new SHA1()),
+                       ToBin("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c"),
+                       Encoding.UTF8.GetBytes("Test With Truncation"),
+                       ToBin("4c1a03424b55e07fe7f27be1d58bb9324a9a5a04"));
+               DoKATHMAC(new HMAC(new SHA1()),
+                       ToBin("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
+                       Encoding.UTF8.GetBytes("Test Using Larger Than Block-Size Key - Hash Key First"),
+                       ToBin("aa4ae5e15272d00e95705637ce8a3b55ed402112"));
+               DoKATHMAC(new HMAC(new SHA1()),
+                       ToBin("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
+                       Encoding.UTF8.GetBytes("Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data"),
+                       ToBin("e8e99d0f45237d786d6bbaa7965c7808bbff1a91"));
+
+               TestHMAC_CT(new MD5());
+               TestHMAC_CT(new SHA1());
+               TestHMAC_CT(new SHA256());
+               TestHMAC_CT(new SHA512());
+
+               Console.WriteLine("done.");
+       }
+
+       static void TestHMAC_CT(IDigest h)
+       {
+               Console.Write("[{0}]", h.Name);
+               int hlen = h.DigestSize;
+               HMAC hm = new HMAC(h);
+               byte[] key = new byte[hlen];
+               RNG.GetBytes(key);
+               hm.SetKey(key);
+               byte[] data = new byte[384];
+               RNG.GetBytes(data);
+               byte[] tmp1 = new byte[hlen];
+               byte[] tmp2 = new byte[hlen];
+               for (int i = 0; i < 256; i ++) {
+                       int i1 = i >> 1;
+                       int i2 = i - i1;
+                       for (int j = 0; j <= 128; j ++) {
+                               hm.Update(data, 0, i1);
+                               hm.ComputeCT(data, i1, i2 + j, i2, i2 + j,
+                                       tmp1, 0);
+                               hm.Update(data, 0, i + j);
+                               hm.DoFinal(tmp2, 0);
+                               CheckEq(tmp1, tmp2, "CT");
+                       }
+                       Console.Write(".");
+               }
+       }
+
+       static void TestHMAC_DRBG()
+       {
+               Console.Write("Testing HMAC_DRBG... ");
+               byte[] tmp = new byte[30];
+
+               HMAC_DRBG drbg = new HMAC_DRBG(new SHA256());
+               drbg.Update(ToBin("009A4D6792295A7F730FC3F2B49CBC0F62E862272F01795EDF0D54DB760F156D0DAC04C0322B3A204224"));
+               drbg.GetBytes(tmp);
+               CheckEq(tmp, ToBin("9305A46DE7FF8EB107194DEBD3FD48AA20D5E7656CBE0EA69D2A8D4E7C67"), "KAT 1");
+               drbg.GetBytes(tmp);
+               CheckEq(tmp, ToBin("C70C78608A3B5BE9289BE90EF6E81A9E2C1516D5751D2F75F50033E45F73"), "KAT 2");
+               drbg.GetBytes(tmp);
+               CheckEq(tmp, ToBin("475E80E992140567FCC3A50DAB90FE84BCD7BB03638E9C4656A06F37F650"), "KAT 3");
+
+               Console.WriteLine("done.");
+       }
+
+       static void TestAES()
+       {
+               Console.Write("Testing AES... ");
+
+               AES bc = new AES();
+               DoKATBlockCipherRaw(bc, KAT_AES_RAW);
+               DoKATBlockCipherCBC(bc, KAT_AES_CBC);
+               DoKATBlockCipherCTR(bc, KAT_AES_CTR);
+
+               DoMonteCarloAESEncrypt(bc,
+                       "139a35422f1d61de3c91787fe0507afd",
+                       "b9145a768b7dc489a096b546f43b231f",
+                       "fb2649694783b551eacd9d5db6126d47");
+               DoMonteCarloAESDecrypt(bc,
+                       "0c60e7bf20ada9baa9e1ddf0d1540726",
+                       "b08a29b11a500ea3aca42c36675b9785",
+                       "d1d2bfdc58ffcad2341b095bce55221e");
+
+               DoMonteCarloAESEncrypt(bc,
+                       "b9a63e09e1dfc42e93a90d9bad739e5967aef672eedd5da9",
+                       "85a1f7a58167b389cddc8a9ff175ee26",
+                       "5d1196da8f184975e240949a25104554");
+               DoMonteCarloAESDecrypt(bc,
+                       "4b97585701c03fbebdfa8555024f589f1482c58a00fdd9fd",
+                       "d0bd0e02ded155e4516be83f42d347a4",
+                       "b63ef1b79507a62eba3dafcec54a6328");
+
+               DoMonteCarloAESEncrypt(bc,
+                       "f9e8389f5b80712e3886cc1fa2d28a3b8c9cd88a2d4a54c6aa86ce0fef944be0",
+                       "b379777f9050e2a818f2940cbbd9aba4",
+                       "c5d2cb3d5b7ff0e23e308967ee074825");
+               DoMonteCarloAESDecrypt(bc,
+                       "2b09ba39b834062b9e93f48373b8dd018dedf1e5ba1b8af831ebbacbc92a2643",
+                       "89649bd0115f30bd878567610223a59d",
+                       "e3d3868f578caf34e36445bf14cefc68");
+
+               Console.WriteLine("done.");
+       }
+
+       static void TestDES()
+       {
+               Console.Write("Testing DES... ");
+
+               DES bc = new DES();
+               DoKATBlockCipherRaw(bc, KAT_DES_RAW);
+               DoKATBlockCipherCBC(bc, KAT_DES_CBC);
+               DoMonteCarloDESEncrypt(bc);
+               DoMonteCarloDESDecrypt(bc);
+
+               Console.WriteLine("done.");
+       }
+
+       static void TestChaCha20()
+       {
+               Console.Write("Testing ChaCha20... ");
+
+               for (int i = 0; i < KAT_CHACHA20.Length; i += 5) {
+                       byte[] key = ToBin(KAT_CHACHA20[i + 0]);
+                       byte[] iv = ToBin(KAT_CHACHA20[i + 1]);
+                       uint cc = UInt32.Parse(KAT_CHACHA20[i + 2]);
+                       byte[] plain = ToBin(KAT_CHACHA20[i + 3]);
+                       byte[] cipher = ToBin(KAT_CHACHA20[i + 4]);
+
+                       ChaCha20 chacha = new ChaCha20();
+                       chacha.SetKey(key);
+                       byte[] tmp = new byte[plain.Length];
+
+                       for (int j = 0; j <= plain.Length; j ++) {
+                               for (int k = 0; k < tmp.Length; k ++) {
+                                       tmp[k] = 0;
+                               }
+                               Array.Copy(plain, 0, tmp, 0, j);
+                               if (chacha.Run(iv, cc, tmp, 0, j)
+                                       != cc + (uint)((j + 63) >> 6))
+                               {
+                                       throw new Exception(
+                                               "ChaCha20: wrong counter");
+                               }
+                               CheckEq(tmp, 0, cipher, 0, j, "KAT 1");
+                               for (int k = j; k < tmp.Length; k ++) {
+                                       if (tmp[k] != 0) {
+                                               throw new Exception(
+                                                       "ChaCha20: overrun");
+                                       }
+                               }
+
+                               uint cc2 = cc;
+                               for (int k = 0; k < j; k += 64) {
+                                       int clen = Math.Min(64, j - k);
+                                       cc2 = chacha.Run(iv, cc2, tmp, k, clen);
+                               }
+                               CheckEq(tmp, 0, plain, 0, j, "KAT 2");
+                       }
+               }
+
+               Console.WriteLine("done.");
+       }
+
+       static void TestPoly1305()
+       {
+               Console.Write("Testing Poly1305... ");
+
+               byte[] plain = ToBin("4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e");
+               byte[] aad = ToBin("50515253c0c1c2c3c4c5c6c7");
+               byte[] key = ToBin("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f");
+               byte[] iv = ToBin("070000004041424344454647");
+               byte[] cipher = ToBin("d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116");
+               byte[] tag = ToBin("1ae10b594f09e26a7e902ecbd0600691");
+
+               byte[] data = new byte[plain.Length];
+               Array.Copy(plain, 0, data, 0, plain.Length);
+               byte[] tt = new byte[16];
+
+               ChaCha20 cc = new ChaCha20();
+               cc.SetKey(key);
+               Poly1305 pp = new Poly1305();
+               pp.ChaCha = cc;
+               pp.Run(iv, data, 0, data.Length, aad, 0, aad.Length, tt, true);
+               CheckEq(data, cipher, "KAT 1");
+               CheckEq(tt, tag, "KAT 2");
+               for (int i = 0; i < tt.Length; i ++) {
+                       tt[i] = 0;
+               }
+               pp.Run(iv, data, 0, data.Length, aad, 0, aad.Length, tt, false);
+               CheckEq(data, plain, "KAT 3");
+               CheckEq(tt, tag, "KAT 4");
+
+               /*
+                * Make random messages and keys, and test the implementation
+                * against the ZInt-based reference code.
+                */
+               Poly1305Ref ppref = new Poly1305Ref();
+               ppref.ChaCha = new ChaCha20();
+               data = new byte[100];
+               aad = new byte[data.Length];
+               byte[] tmp = new byte[data.Length];
+               key = new byte[32];
+               iv = new byte[12];
+               byte[] tag1 = new byte[16];
+               byte[] tag2 = new byte[16];
+               for (int i = 0; i < data.Length; i ++) {
+                       RNG.GetBytes(key);
+                       RNG.GetBytes(iv);
+                       RNG.GetBytes(data, 0, i);
+                       RNG.GetBytes(aad, 0, i);
+
+                       Array.Copy(data, 0, tmp, 0, i);
+                       for (int j = i; j < tmp.Length; j ++) {
+                               tmp[j] = 0xFF;
+                       }
+                       pp.ChaCha.SetKey(key);
+                       pp.Run(iv, tmp, 0, i, aad, 0, i, tag1, true);
+
+                       for (int j = i; j < tmp.Length; j ++) {
+                               tmp[j] = 0x00;
+                       }
+                       ppref.ChaCha.SetKey(key);
+                       ppref.Run(iv, tmp, 0, i, aad, 0, i, tag2, false);
+
+                       CheckEq(data, 0, tmp, 0, i, "cross enc/dec");
+                       CheckEq(tag1, tag2, "cross MAC");
+                       Console.Write(".");
+               }
+
+               Console.WriteLine("done.");
+       }
+
+       static void TestGHASH()
+       {
+               Console.Write("Testing GHASH... ");
+
+               for (int i = 0; i < KAT_GHASH.Length; i += 4) {
+                       byte[] h = ToBin(KAT_GHASH[i]);
+                       byte[] a = ToBin(KAT_GHASH[i + 1]);
+                       byte[] c = ToBin(KAT_GHASH[i + 2]);
+                       byte[] r = ToBin(KAT_GHASH[i + 3]);
+                       byte[] y = new byte[16];
+                       byte[] p = new byte[16];
+                       GHASH.Run(y, h, a);
+                       GHASH.Run(y, h, c);
+                       uint alen = (uint)a.Length << 3;
+                       uint clen = (uint)c.Length << 3;
+                       p[ 4] = (byte)(alen >> 24);
+                       p[ 5] = (byte)(alen >> 16);
+                       p[ 6] = (byte)(alen >>  8);
+                       p[ 7] = (byte)alen;
+                       p[12] = (byte)(clen >> 24);
+                       p[13] = (byte)(clen >> 16);
+                       p[14] = (byte)(clen >>  8);
+                       p[15] = (byte)clen;
+                       GHASH.Run(y, h, p);
+                       CheckEq(y, r, "KAT " + (i / 4 + 1));
+               }
+
+               Console.WriteLine("done.");
+       }
+
+       static void TestRSA()
+       {
+               Console.Write("Testing RSA... ");
+
+               DoRSASelfTests(1024);
+               DoRSASelfTests(1231);
+               DoRSASelfTests(2048);
+
+               Console.WriteLine("done.");
+       }
+
+       static void PrintInt(string name, byte[] x)
+       {
+               Console.Write("{0} = 0x", name);
+               foreach (byte b in x) {
+                       Console.Write("{0:X2}", b);
+               }
+               Console.WriteLine();
+       }
+
+       static ZInt MakePrime(int size)
+       {
+               ZInt m = ((ZInt)3 << (size - 2)) + (ZInt)3;
+               for (;;) {
+                       ZInt r = ZInt.MakeRand(size - 4);
+                       ZInt p = (r << 2) + m;
+                       if (p % (ZInt)65537 != 1 && p.IsPrime) {
+                               return p;
+                       }
+               }
+       }
+
+       static ZInt ExtendedGCD(ZInt a, ZInt b, out ZInt u, out ZInt v)
+       {
+               ZInt s = 0, sp = 1;
+               ZInt t = 1, tp = 0;
+               while (b != 0) {
+                       ZInt q = a / b;
+                       ZInt bn = a - q * b;
+                       ZInt sn = sp - q * s;
+                       ZInt tn = tp - q * t;
+                       a = b;
+                       sp = s;
+                       tp = t;
+                       b = bn;
+                       s = sn;
+                       t = tn;
+               }
+               u = sp;
+               v = tp;
+               return a;
+       }
+
+       static ZInt ModInverse(ZInt x, ZInt n)
+       {
+               ZInt u, v;
+               ZInt d = ExtendedGCD(x, n, out u, out v);
+               if (x * u + v * n != d) {
+                       throw new Exception("bad extended GCD");
+               }
+               if (d != 1) {
+                       throw new Exception("not invertible");
+               }
+               return u.Mod(n);
+       }
+
+       static void DoRSASelfTests(int size)
+       {
+               Console.Write("   key gen ({0}): ", size);
+               long begin = DateTime.UtcNow.Ticks;
+
+               /*
+                * Use ZInt to generate new RSA key.
+                */
+               ZInt p = MakePrime(size - (size >> 1));
+               ZInt q;
+               do {
+                       q = MakePrime(size >> 1);
+               } while (p == q);
+               if (p < q) {
+                       ZInt t = p;
+                       p = q;
+                       q = t;
+               }
+               ZInt n = p * q;
+               ZInt e = 65537;
+               ZInt d = ModInverse(65537, (p - 1) * (q - 1));
+               ZInt dp = d % (p - 1);
+               ZInt dq = d % (q - 1);
+               ZInt iq = ModInverse(q, p);
+
+               long end = DateTime.UtcNow.Ticks;
+               Console.WriteLine("done in {0} ms.", (end - begin) / 10000);
+
+               /*
+               Console.WriteLine("n  = {0}", n);
+               Console.WriteLine("e  = {0}", e);
+               Console.WriteLine("d  = {0}", d);
+               Console.WriteLine("p  = {0}", p);
+               Console.WriteLine("q  = {0}", q);
+               Console.WriteLine("dp = {0}", dp);
+               Console.WriteLine("dq = {0}", dq);
+               Console.WriteLine("iq = {0}", iq);
+               */
+
+               RSAPrivateKey sk = new RSAPrivateKey(
+                       n.ToBytesUnsignedBE(),
+                       e.ToBytesUnsignedBE(),
+                       d.ToBytesUnsignedBE(),
+                       p.ToBytesUnsignedBE(),
+                       q.ToBytesUnsignedBE(),
+                       dp.ToBytesUnsignedBE(),
+                       dq.ToBytesUnsignedBE(),
+                       iq.ToBytesUnsignedBE());
+
+               /*
+               Console.Write("   key gen ({0}): ", size);
+               long begin = DateTime.UtcNow.Ticks;
+               RSAPrivateKey sk = RSA.Generate(size);
+               long end = DateTime.UtcNow.Ticks;
+               Console.WriteLine("done in {0} ms.", (end - begin) / 10000);
+               */
+
+               /*
+               PrintInt("n ", sk.N);
+               PrintInt("e ", sk.E);
+               PrintInt("d ", sk.D);
+               PrintInt("p ", sk.P);
+               PrintInt("q ", sk.Q);
+               PrintInt("dp", sk.DP);
+               PrintInt("dq", sk.DQ);
+               PrintInt("iq", sk.IQ);
+               */
+
+               RSAPublicKey pk = (RSAPublicKey)sk.PublicKey;
+               for (int i = 0; i < ((size + 7) >> 3) - 11; i ++) {
+                       byte[] msg = new byte[i];
+                       RNG.GetBytes(msg);
+                       IDigest h = new SHA256();
+                       h.Update(msg);
+                       byte[] hv = h.DoFinal();
+                       byte[] sig = RSA.Sign(sk, RSA.PKCS1_SHA256, hv);
+                       if (!RSA.Verify(pk, RSA.PKCS1_SHA256, null, hv, sig)) {
+                               throw new Exception(String.Format(
+                                       "RSA sign/verify 1 (len = {0})", i));
+                       }
+                       if (!RSA.Verify(pk, RSA.PKCS1_SHA256_ALT,
+                               RSA.PKCS1_SHA256, hv, sig))
+                       {
+                               throw new Exception(String.Format(
+                                       "RSA sign/verify 2 (len = {0})", i));
+                       }
+                       if (RSA.Verify(pk, RSA.PKCS1_SHA1, null, hv, sig)) {
+                               throw new Exception(String.Format(
+                                       "RSA sign/verify 3 (len = {0})", i));
+                       }
+                       hv[21] ^= 0x01;
+                       if (RSA.Verify(pk, RSA.PKCS1_SHA256, null, hv, sig)) {
+                               throw new Exception(String.Format(
+                                       "RSA sign/verify 4 (len = {0})", i));
+                       }
+
+                       byte[] enc = RSA.Encrypt(pk, msg);
+                       byte[] dec = RSA.Decrypt(sk, enc);
+                       if (!Eq(msg, dec)) {
+                               throw new Exception(String.Format(
+                                       "RSA encrypt/decrypt (len = {0})", i));
+                       }
+               }
+       }
+
+       static void TestECDSA()
+       {
+               Console.WriteLine("Testing ECDSA... ");
+
+               DoKATECDSA(NIST.P256, ECDSA_K_P256, ECDSA_SIGS_P256);
+               DoKATECDSA(NIST.P384, ECDSA_K_P384, ECDSA_SIGS_P384);
+               DoKATECDSA(NIST.P521, ECDSA_K_P521, ECDSA_SIGS_P521);
+               DoECDSASelfTests(NIST.P256);
+               DoECDSASelfTests(NIST.P384);
+               DoECDSASelfTests(NIST.P521);
+
+               Console.WriteLine("done.");
+       }
+
+       static void DoKATECDSA(ECCurve curve, string[] ks, string[] sigs)
+       {
+               Console.Write("   KAT ({0}): ", curve.Name);
+               ECPublicKey pk = new ECPublicKey(curve, ToBin(ks[0]));
+               pk.CheckValid();
+               Console.Write("<valid pub> ");
+               ECPrivateKey sk = new ECPrivateKey(curve, ToBin(ks[1]));
+               sk.CheckValid();
+               Console.Write("<valid priv> ");
+               IPublicKey pk2 = sk.PublicKey;
+               if (!pk.Equals(pk2) || !pk2.Equals(pk)) {
+                       throw new Exception("ECDSA mismatch public/private");
+               }
+               if (!pk.Equals(pk2) || !pk2.Equals(pk)) {
+                       throw new Exception("ECDSA mismatch public/private");
+               }
+               for (int i = 0; i < 10; i ++) {
+                       byte[] r = ToBin(sigs[i << 1]);
+                       byte[] s = ToBin(sigs[(i << 1) + 1]);
+                       byte[] msg = Encoding.UTF8.GetBytes(
+                               (i < 5) ? "sample" : "test");
+                       IDigest dig;
+                       switch (i % 5) {
+                       case 0:  dig = new SHA1(); break;
+                       case 1:  dig = new SHA224(); break;
+                       case 2:  dig = new SHA256(); break;
+                       case 3:  dig = new SHA384(); break;
+                       default: dig = new SHA512(); break;
+                       }
+                       dig.Update(msg);
+                       byte[] hv = dig.DoFinal();
+                       DoKATECDSA(pk, sk, dig, r, s, hv);
+                       Console.Write(".");
+               }
+               Console.WriteLine();
+       }
+
+       static void DoKATECDSA(ECPublicKey pk, ECPrivateKey sk,
+               IDigest dig, byte[] r, byte[] s, byte[] hv)
+       {
+               if (r.Length != s.Length) {
+                       throw new ArgumentException();
+               }
+               byte[] sig1 = new byte[r.Length + s.Length];
+               Array.Copy(r, 0, sig1, 0, r.Length);
+               Array.Copy(s, 0, sig1, r.Length, s.Length);
+               byte[] sig2 = ECDSA.SigRawToAsn1(sig1);
+               byte[] sig3 = ECDSA.SigRawToAsn1(ECDSA.SigAsn1ToRaw(sig2));
+               if (!Eq(sig2, sig3)) {
+                       throw new Exception("ECDSA sig enc/dec");
+               }
+
+               if (!ECDSA.VerifyRaw(pk, hv, sig1)) {
+                       throw new Exception("ECDSA verify 1");
+               }
+               if (!ECDSA.Verify(pk, hv, sig2)) {
+                       throw new Exception("ECDSA verify 2");
+               }
+               byte[] hv2 = new byte[hv.Length + 2];
+               Array.Copy(hv, 0, hv2, 1, hv.Length);
+               if (!ECDSA.VerifyRaw(pk, hv2, 1, hv.Length, sig1)) {
+                       throw new Exception("ECDSA verify 3");
+               }
+               hv2[1] ^= (byte)0x02;
+               if (ECDSA.VerifyRaw(pk, hv2, 1, hv.Length, sig1)) {
+                       throw new Exception("ECDSA verify 4");
+               }
+
+               byte[] sig4 = ECDSA.SignRaw(sk, null, hv);
+               if (Eq(sig1, sig4)) {
+                       throw new Exception("ECDSA sig randomized");
+               }
+               if (!ECDSA.VerifyRaw(pk, hv, sig4)) {
+                       throw new Exception("ECDSA verify 5");
+               }
+               byte[] sig5 = ECDSA.SignRaw(sk, dig, hv);
+               if (!Eq(sig1, sig5)) {
+                       throw new Exception("ECDSA sig deterministic");
+               }
+       }
+
+       static void DoECDSASelfTests(ECCurve curve)
+       {
+               Console.Write("   self ({0}): ", curve.Name);
+               ECPrivateKey sk = ECDSA.Generate(curve);
+               sk.CheckValid();
+               Console.Write("<valid> ");
+               ECPublicKey pk = sk.PublicKey;
+               IDigest h = new SHA256();
+               byte[] msg = new byte[32];
+               for (int i = 0; i < 10; i ++) {
+                       RNG.GetBytes(msg);
+                       byte[] hv = h.Hash(msg);
+                       byte[] sig = ECDSA.Sign(sk, null, hv);
+                       if (!ECDSA.Verify(pk, hv, sig)) {
+                               throw new Exception("ECDSA sign/verify");
+                       }
+                       Console.Write(".");
+               }
+               Console.WriteLine();
+       }
+
+       static void DoKATHash(IDigest h, byte[] data, byte[] refOut)
+       {
+               h.Update(data);
+               CheckEq(h.DoFinal(), refOut, "KAT 1");
+               h.Update(data, 0, data.Length);
+               byte[] tmp = new byte[h.DigestSize];
+               h.DoFinal(tmp, 0);
+               CheckEq(tmp, refOut, "KAT 2");
+               foreach (byte b in data) {
+                       h.Update(b);
+               }
+               CheckEq(h.DoFinal(), refOut, "KAT 3");
+               for (int t = 0; t < data.Length; t ++) {
+                       h.Update(data, 0, t);
+                       h.Update(data, t, data.Length - t);
+                       h.DoFinal(tmp, 0);
+                       CheckEq(tmp, refOut, "KAT 4." + t);
+               }
+               foreach (byte b in data) {
+                       h.Update(b);
+                       h.DoPartial(tmp, 0);
+               }
+               CheckEq(tmp, refOut, "KAT 5");
+               h.Reset();
+               h.Update(data);
+               CheckEq(h.DoFinal(), refOut, "KAT 6");
+               for (int t = 0; t < data.Length; t ++) {
+                       h.Update(data, 0, t);
+                       IDigest h2 = h.Dup();
+                       h.Update(data, t, data.Length - t);
+                       h.DoFinal(tmp, 0);
+                       CheckEq(tmp, refOut, "KAT 7." + t);
+                       h2.Update(data, t, data.Length - t);
+                       h2.DoFinal(tmp, 0);
+                       CheckEq(tmp, refOut, "KAT 8." + t);
+               }
+       }
+
+       static void DoKATHashLong(IDigest h, string refOut)
+       {
+               byte[] buf = new byte[1000];
+               for (int i = 0; i < buf.Length; i ++) {
+                       buf[i] = (byte)'a';
+               }
+               for (int i = 0; i < 1000; i ++) {
+                       h.Update(buf);
+               }
+               CheckEq(h.DoFinal(), ToBin(refOut), "KAT Long");
+       }
+
+       static void DoKATHMAC(HMAC hm, byte[] key, byte[] data, byte[] refOut)
+       {
+               hm.SetKey(key);
+               hm.Update(data);
+               CheckEq(hm.DoFinal(), refOut, "KAT 1");
+               hm.Update(data, 0, data.Length);
+               byte[] tmp = new byte[hm.MACSize];
+               hm.DoFinal(tmp, 0);
+               CheckEq(tmp, refOut, "KAT 2");
+               foreach (byte b in data) {
+                       hm.Update(b);
+               }
+               CheckEq(hm.DoFinal(), refOut, "KAT 3");
+               for (int t = 0; t < data.Length; t ++) {
+                       hm.Update(data, 0, t);
+                       hm.Update(data, t, data.Length - t);
+                       hm.DoFinal(tmp, 0);
+                       CheckEq(tmp, refOut, "KAT 4." + t);
+               }
+               for (int t = 0; t < data.Length; t ++) {
+                       hm.Update(data, 0, t);
+                       HMAC hm2 = hm.Dup();
+                       hm.Update(data, t, data.Length - t);
+                       hm.DoFinal(tmp, 0);
+                       CheckEq(tmp, refOut, "KAT 5." + t);
+                       hm2.Update(data, t, data.Length - t);
+                       hm2.DoFinal(tmp, 0);
+                       CheckEq(tmp, refOut, "KAT 6." + t);
+               }
+       }
+
+       static void DoKATBlockCipherRaw(IBlockCipher bc, string[] kat)
+       {
+               for (int i = 0; i < kat.Length; i += 3) {
+                       byte[] key = ToBin(kat[i]);
+                       byte[] plain = ToBin(kat[i + 1]);
+                       byte[] cipher = ToBin(kat[i + 2]);
+                       int blen = bc.BlockSize;
+                       if (blen != plain.Length || blen != cipher.Length) {
+                               throw new Exception(string.Format(
+                                       "block size mismatch: {0} / {1},{2}",
+                                       blen, plain.Length, cipher.Length));
+                       }
+                       bc.SetKey(key);
+                       byte[] tmp = new byte[blen];
+                       Array.Copy(plain, 0, tmp, 0, blen);
+                       bc.BlockEncrypt(tmp, 0);
+                       CheckEq(tmp, cipher, "KAT encrypt");
+                       bc.BlockDecrypt(tmp, 0);
+                       CheckEq(tmp, plain, "KAT decrypt");
+               }
+       }
+
+       static void DoKATBlockCipherCBC(IBlockCipher bc, string[] kat)
+       {
+               for (int i = 0; i < kat.Length; i += 4) {
+                       byte[] key = ToBin(kat[i]);
+                       byte[] iv = ToBin(kat[i + 1]);
+                       byte[] plain = ToBin(kat[i + 2]);
+                       byte[] cipher = ToBin(kat[i + 3]);
+                       int blen = bc.BlockSize;
+                       if (blen != iv.Length
+                               || (plain.Length % blen) != 0
+                               || (cipher.Length % blen) != 0
+                               || plain.Length != cipher.Length)
+                       {
+                               throw new Exception(string.Format(
+                                       "block size mismatch:"
+                                       + " {0} / {1},{2},{3}",
+                                       blen, iv.Length,
+                                       plain.Length, cipher.Length));
+                       }
+                       bc.SetKey(key);
+                       byte[] tmp = new byte[plain.Length];
+                       Array.Copy(plain, 0, tmp, 0, tmp.Length);
+                       bc.CBCEncrypt(iv, tmp);
+                       CheckEq(tmp, cipher, "KAT CBC encrypt (1)");
+                       bc.CBCDecrypt(iv, tmp);
+                       CheckEq(tmp, plain, "KAT CBC decrypt (1)");
+
+                       byte[] iv2 = new byte[blen];
+                       Array.Copy(iv, 0, iv2, 0, blen);
+                       for (int j = 0; j < tmp.Length; j += blen) {
+                               bc.CBCEncrypt(iv2, tmp, j, blen);
+                               Array.Copy(tmp, j, iv2, 0, blen);
+                       }
+                       CheckEq(tmp, cipher, "KAT CBC encrypt (2)");
+                       byte[] iv3 = new byte[blen];
+                       Array.Copy(iv, 0, iv2, 0, blen);
+                       for (int j = 0; j < tmp.Length; j += blen) {
+                               Array.Copy(tmp, j, iv3, 0, blen);
+                               bc.CBCDecrypt(iv2, tmp, j, blen);
+                               Array.Copy(iv3, 0, iv2, 0, blen);
+                       }
+                       CheckEq(tmp, plain, "KAT CBC decrypt (2)");
+               }
+       }
+
+       static void DoKATBlockCipherCTR(IBlockCipher bc, string[] kat)
+       {
+               for (int i = 0; i < kat.Length; i += 4) {
+                       byte[] key = ToBin(kat[i]);
+                       byte[] iv = ToBin(kat[i + 1]);
+                       byte[] plain = ToBin(kat[i + 2]);
+                       byte[] cipher = ToBin(kat[i + 3]);
+                       int blen = bc.BlockSize;
+                       if (blen != (iv.Length + 4)
+                               || plain.Length != cipher.Length)
+                       {
+                               throw new Exception(string.Format(
+                                       "block size mismatch:"
+                                       + " {0} / {1},{2},{3}",
+                                       blen, iv.Length,
+                                       plain.Length, cipher.Length));
+                       }
+                       bc.SetKey(key);
+                       byte[] tmp = new byte[plain.Length];
+                       Array.Copy(plain, 0, tmp, 0, tmp.Length);
+                       uint cc;
+                       cc = bc.CTRRun(iv, 1, tmp);
+                       CheckEq(tmp, cipher, "KAT CTR encrypt (1)");
+                       if (cc != 1 + ((tmp.Length + blen - 1) / blen)) {
+                               throw new Exception(string.Format(
+                                       "wrong CTR counter: {0} / {1}",
+                                       cc, tmp.Length));
+                       }
+                       cc = bc.CTRRun(iv, 1, tmp);
+                       CheckEq(tmp, plain, "KAT CTR decrypt (1)");
+                       if (cc != 1 + ((tmp.Length + blen - 1) / blen)) {
+                               throw new Exception(string.Format(
+                                       "wrong CTR counter: {0} / {1}",
+                                       cc, tmp.Length));
+                       }
+
+                       cc = 1;
+                       for (int j = 0; j < tmp.Length; j += blen) {
+                               int clen = Math.Min(blen, tmp.Length - j);
+                               uint cc2 = bc.CTRRun(iv, cc, tmp, j, clen);
+                               if (cc2 != cc + 1) {
+                                       throw new Exception(
+                                               "wrong CTR counter update");
+                               }
+                               cc = cc2;
+                       }
+                       CheckEq(tmp, cipher, "KAT CTR encrypt (2)");
+               }
+       }
+
+       static void DoMonteCarloAESEncrypt(IBlockCipher bc,
+               string skey, string splain, string scipher)
+       {
+               byte[] key = ToBin(skey);
+               byte[] buf = ToBin(splain);
+               byte[] pbuf = new byte[buf.Length];
+               byte[] cipher = ToBin(scipher);
+               for (int i = 0; i < 100; i ++) {
+                       bc.SetKey(key);
+                       for (int j = 0; j < 1000; j ++) {
+                               Array.Copy(buf, 0, pbuf, 0, pbuf.Length);
+                               bc.BlockEncrypt(buf);
+                       }
+                       switch (key.Length) {
+                       case 16:
+                               for (int k = 0; k < 16; k ++) {
+                                       key[k] ^= buf[k];
+                               }
+                               break;
+                       case 24:
+                               for (int k = 0; k < 8; k ++) {
+                                       key[k] ^= pbuf[8 + k];
+                               }
+                               for (int k = 0; k < 16; k ++) {
+                                       key[8 + k] ^= buf[k];
+                               }
+                               break;
+                       case 32:
+                               for (int k = 0; k < 16; k ++) {
+                                       key[k] ^= pbuf[k];
+                               }
+                               for (int k = 0; k < 16; k ++) {
+                                       key[16 + k] ^= buf[k];
+                               }
+                               break;
+                       }
+                       Console.Write(".");
+               }
+               Console.Write(" ");
+               CheckEq(buf, cipher, "MC AES encrypt");
+       }
+
+       static void DoMonteCarloAESDecrypt(IBlockCipher bc,
+               string skey, string splain, string scipher)
+       {
+               byte[] key = ToBin(skey);
+               byte[] buf = ToBin(splain);
+               byte[] pbuf = new byte[buf.Length];
+               byte[] cipher = ToBin(scipher);
+               for (int i = 0; i < 100; i ++) {
+                       bc.SetKey(key);
+                       for (int j = 0; j < 1000; j ++) {
+                               Array.Copy(buf, 0, pbuf, 0, pbuf.Length);
+                               bc.BlockDecrypt(buf);
+                       }
+                       switch (key.Length) {
+                       case 16:
+                               for (int k = 0; k < 16; k ++) {
+                                       key[k] ^= buf[k];
+                               }
+                               break;
+                       case 24:
+                               for (int k = 0; k < 8; k ++) {
+                                       key[k] ^= pbuf[8 + k];
+                               }
+                               for (int k = 0; k < 16; k ++) {
+                                       key[8 + k] ^= buf[k];
+                               }
+                               break;
+                       case 32:
+                               for (int k = 0; k < 16; k ++) {
+                                       key[k] ^= pbuf[k];
+                               }
+                               for (int k = 0; k < 16; k ++) {
+                                       key[16 + k] ^= buf[k];
+                               }
+                               break;
+                       }
+                       Console.Write(".");
+               }
+               Console.Write(" ");
+               CheckEq(buf, cipher, "MC AES decrypt");
+       }
+
+       static void DoMonteCarloDESEncrypt(IBlockCipher bc)
+       {
+               byte[] k1 = ToBin("9ec2372c86379df4");
+               byte[] k2 = ToBin("ad7ac4464f73805d");
+               byte[] k3 = ToBin("20c4f87564527c91");
+               byte[] buf = ToBin("b624d6bd41783ab1");
+               byte[] cipher = ToBin("eafd97b190b167fe");
+               byte[] key = new byte[24];
+               for (int i = 0; i < 400; i ++) {
+                       Array.Copy(k1, 0, key, 0, 8);
+                       Array.Copy(k2, 0, key, 8, 8);
+                       Array.Copy(k3, 0, key, 16, 8);
+                       bc.SetKey(key);
+                       for (int j = 0; j < 10000; j ++) {
+                               bc.BlockEncrypt(buf, 0);
+                               switch (j) {
+                               case 9997:
+                                       for (int n = 0; n < 8; n ++) {
+                                               k3[n] ^= buf[n];
+                                       }
+                                       break;
+                               case 9998:
+                                       for (int n = 0; n < 8; n ++) {
+                                               k2[n] ^= buf[n];
+                                       }
+                                       break;
+                               case 9999:
+                                       for (int n = 0; n < 8; n ++) {
+                                               k1[n] ^= buf[n];
+                                       }
+                                       break;
+                               }
+                       }
+                       Console.Write(".");
+               }
+               Console.Write(" ");
+               CheckEq(buf, cipher, "MC DES encrypt");
+       }
+
+       static void DoMonteCarloDESDecrypt(IBlockCipher bc)
+       {
+               byte[] k1 = ToBin("79b63486e0ce37e0");
+               byte[] k2 = ToBin("08e65231abae3710");
+               byte[] k3 = ToBin("1f5eb69e925ef185");
+               byte[] buf = ToBin("2783aa729432fe96");
+               byte[] cipher = ToBin("44937ca532cdbf98");
+               byte[] key = new byte[24];
+               for (int i = 0; i < 400; i ++) {
+                       Array.Copy(k1, 0, key, 0, 8);
+                       Array.Copy(k2, 0, key, 8, 8);
+                       Array.Copy(k3, 0, key, 16, 8);
+                       bc.SetKey(key);
+                       for (int j = 0; j < 10000; j ++) {
+                               bc.BlockDecrypt(buf, 0);
+                               switch (j) {
+                               case 9997:
+                                       for (int n = 0; n < 8; n ++) {
+                                               k3[n] ^= buf[n];
+                                       }
+                                       break;
+                               case 9998:
+                                       for (int n = 0; n < 8; n ++) {
+                                               k2[n] ^= buf[n];
+                                       }
+                                       break;
+                               case 9999:
+                                       for (int n = 0; n < 8; n ++) {
+                                               k1[n] ^= buf[n];
+                                       }
+                                       break;
+                               }
+                       }
+                       Console.Write(".");
+               }
+               Console.Write(" ");
+               CheckEq(buf, cipher, "MC DES decrypt");
+       }
+
+       static bool Eq(byte[] a1, byte[] a2)
+       {
+               if (a1 == a2) {
+                       return true;
+               }
+               if (a1 == null || a2 == null) {
+                       return false;
+               }
+               int n = a1.Length;
+               if (n != a2.Length) {
+                       return false;
+               }
+               for (int i = 0; i < n; i ++) {
+                       if (a1[i] != a2[i]) {
+                               return false;
+                       }
+               }
+               return true;
+       }
+
+       static bool Eq(byte[] a1, int off1, byte[] a2, int off2, int len)
+       {
+               for (int i = 0; i < len; i ++) {
+                       if (a1[off1 + i] != a2[off1 + i]) {
+                               return false;
+                       }
+               }
+               return true;
+       }
+
+       static void CheckEq(byte[] a1, byte[] a2, string msg)
+       {
+               if (Eq(a1, a2)) {
+                       return;
+               }
+               throw new Exception(string.Format(
+                       "Not equal ({0}):\nv1 = {1}\nv2 = {2}",
+                       msg, ToHex(a1), ToHex(a2)));
+       }
+
+       static void CheckEq(byte[] a1, int off1,
+               byte[] a2, int off2, int len, string msg)
+       {
+               if (Eq(a1, off1, a2, off2, len)) {
+                       return;
+               }
+               throw new Exception(string.Format(
+                       "Not equal ({0}):\nv1 = {1}\nv2 = {2}",
+                       msg, ToHex(a1, off1, len), ToHex(a2, off2, len)));
+       }
+
+       static byte[] ToBin(string str)
+       {
+               MemoryStream ms = new MemoryStream();
+               bool z = true;
+               int acc = 0;
+               foreach (char c in str) {
+                       int d;
+                       if (c >= '0' && c <= '9') {
+                               d = c - '0';
+                       } else if (c >= 'A' && c <= 'F') {
+                               d = c - ('A' - 10);
+                       } else if (c >= 'a' && c <= 'f') {
+                               d = c - ('a' - 10);
+                       } else if (c == ' ' || c == '\t' || c == ':') {
+                               continue;
+                       } else {
+                               throw new ArgumentException(String.Format(
+                                       "not hex: U+{0:X4}", (int)c));
+                       }
+                       if (z) {
+                               acc = d;
+                       } else {
+                               ms.WriteByte((byte)((acc << 4) + d));
+                       }
+                       z = !z;
+               }
+               if (!z) {
+                       throw new ArgumentException("final half byte");
+               }
+               return ms.ToArray();
+       }
+
+       static string ToHex(byte[] buf)
+       {
+               return ToHex(buf, 0, buf.Length);
+       }
+
+       static string ToHex(byte[] buf, int off, int len)
+       {
+               StringBuilder sb = new StringBuilder();
+               for (int i = 0; i < len; i ++) {
+                       sb.AppendFormat("{0:X2}", buf[off + i]);
+               }
+               return sb.ToString();
+       }
+
+       static string[] KAT_MD5 = {
+               "",
+               "d41d8cd98f00b204e9800998ecf8427e",
+               "a",
+               "0cc175b9c0f1b6a831c399e269772661",
+               "abc",
+               "900150983cd24fb0d6963f7d28e17f72",
+               "message digest",
+               "f96b697d7cb7938d525a2f31aaf161d0",
+               "abcdefghijklmnopqrstuvwxyz",
+               "c3fcd3d76192e4007dfb496cca67e13b",
+               "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
+               "d174ab98d277d9f5a5611c2c9f419d9f",
+               "12345678901234567890123456789012345678901234567890123456789012345678901234567890",
+               "57edf4a22be3c955ac49da2e2107b67a"
+       };
+
+       static string[] KAT_SHA1 = {
+               "abc",
+               "a9993e364706816aba3e25717850c26c9cd0d89d",
+               "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+               "84983e441c3bd26ebaae4aa1f95129e5e54670f1"
+       };
+
+       static string[] KAT_SHA224 = {
+               "abc",
+               "23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7",
+               "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+               "75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525"
+       };
+
+       static string[] KAT_SHA256 = {
+               "abc",
+               "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
+               "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+               "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"
+       };
+
+       static string[] KAT_SHA384 = {
+               "abc",
+               "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7",
+               "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+               "09330c33f71147e83d192fc782cd1b4753111b173b3b05d22fa08086e3b0f712fcc7c71a557e2db966c3e9fa91746039"
+       };
+
+       static string[] KAT_SHA512 = {
+               "abc",
+               "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
+               "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+               "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909"
+       };
+
+       /*
+        * AES known-answer tests.
+        * Order: key, plaintext, ciphertext.
+        */
+       static string[] KAT_AES_RAW = {
+               /*
+                * From FIPS-197.
+                */
+               "000102030405060708090a0b0c0d0e0f",
+               "00112233445566778899aabbccddeeff",
+               "69c4e0d86a7b0430d8cdb78070b4c55a",
+
+               "000102030405060708090a0b0c0d0e0f1011121314151617",
+               "00112233445566778899aabbccddeeff",
+               "dda97ca4864cdfe06eaf70a0ec0d7191",
+
+               "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+               "00112233445566778899aabbccddeeff",
+               "8ea2b7ca516745bfeafc49904b496089",
+
+               /*
+                * From NIST validation suite (ECBVarTxt128.rsp).
+                */
+               "00000000000000000000000000000000",
+               "80000000000000000000000000000000",
+               "3ad78e726c1ec02b7ebfe92b23d9ec34",
+
+               "00000000000000000000000000000000",
+               "c0000000000000000000000000000000",
+               "aae5939c8efdf2f04e60b9fe7117b2c2",
+
+               "00000000000000000000000000000000",
+               "e0000000000000000000000000000000",
+               "f031d4d74f5dcbf39daaf8ca3af6e527",
+
+               "00000000000000000000000000000000",
+               "f0000000000000000000000000000000",
+               "96d9fd5cc4f07441727df0f33e401a36",
+
+               "00000000000000000000000000000000",
+               "f8000000000000000000000000000000",
+               "30ccdb044646d7e1f3ccea3dca08b8c0",
+
+               "00000000000000000000000000000000",
+               "fc000000000000000000000000000000",
+               "16ae4ce5042a67ee8e177b7c587ecc82",
+
+               "00000000000000000000000000000000",
+               "fe000000000000000000000000000000",
+               "b6da0bb11a23855d9c5cb1b4c6412e0a",
+
+               "00000000000000000000000000000000",
+               "ff000000000000000000000000000000",
+               "db4f1aa530967d6732ce4715eb0ee24b",
+
+               "00000000000000000000000000000000",
+               "ff800000000000000000000000000000",
+               "a81738252621dd180a34f3455b4baa2f",
+
+               "00000000000000000000000000000000",
+               "ffc00000000000000000000000000000",
+               "77e2b508db7fd89234caf7939ee5621a",
+
+               "00000000000000000000000000000000",
+               "ffe00000000000000000000000000000",
+               "b8499c251f8442ee13f0933b688fcd19",
+
+               "00000000000000000000000000000000",
+               "fff00000000000000000000000000000",
+               "965135f8a81f25c9d630b17502f68e53",
+
+               "00000000000000000000000000000000",
+               "fff80000000000000000000000000000",
+               "8b87145a01ad1c6cede995ea3670454f",
+
+               "00000000000000000000000000000000",
+               "fffc0000000000000000000000000000",
+               "8eae3b10a0c8ca6d1d3b0fa61e56b0b2",
+
+               "00000000000000000000000000000000",
+               "fffe0000000000000000000000000000",
+               "64b4d629810fda6bafdf08f3b0d8d2c5",
+
+               "00000000000000000000000000000000",
+               "ffff0000000000000000000000000000",
+               "d7e5dbd3324595f8fdc7d7c571da6c2a",
+
+               "00000000000000000000000000000000",
+               "ffff8000000000000000000000000000",
+               "f3f72375264e167fca9de2c1527d9606",
+
+               "00000000000000000000000000000000",
+               "ffffc000000000000000000000000000",
+               "8ee79dd4f401ff9b7ea945d86666c13b",
+
+               "00000000000000000000000000000000",
+               "ffffe000000000000000000000000000",
+               "dd35cea2799940b40db3f819cb94c08b",
+
+               "00000000000000000000000000000000",
+               "fffff000000000000000000000000000",
+               "6941cb6b3e08c2b7afa581ebdd607b87",
+
+               "00000000000000000000000000000000",
+               "fffff800000000000000000000000000",
+               "2c20f439f6bb097b29b8bd6d99aad799",
+
+               "00000000000000000000000000000000",
+               "fffffc00000000000000000000000000",
+               "625d01f058e565f77ae86378bd2c49b3",
+
+               "00000000000000000000000000000000",
+               "fffffe00000000000000000000000000",
+               "c0b5fd98190ef45fbb4301438d095950",
+
+               "00000000000000000000000000000000",
+               "ffffff00000000000000000000000000",
+               "13001ff5d99806efd25da34f56be854b",
+
+               "00000000000000000000000000000000",
+               "ffffff80000000000000000000000000",
+               "3b594c60f5c8277a5113677f94208d82",
+
+               "00000000000000000000000000000000",
+               "ffffffc0000000000000000000000000",
+               "e9c0fc1818e4aa46bd2e39d638f89e05",
+
+               "00000000000000000000000000000000",
+               "ffffffe0000000000000000000000000",
+               "f8023ee9c3fdc45a019b4e985c7e1a54",
+
+               "00000000000000000000000000000000",
+               "fffffff0000000000000000000000000",
+               "35f40182ab4662f3023baec1ee796b57",
+
+               "00000000000000000000000000000000",
+               "fffffff8000000000000000000000000",
+               "3aebbad7303649b4194a6945c6cc3694",
+
+               "00000000000000000000000000000000",
+               "fffffffc000000000000000000000000",
+               "a2124bea53ec2834279bed7f7eb0f938",
+
+               "00000000000000000000000000000000",
+               "fffffffe000000000000000000000000",
+               "b9fb4399fa4facc7309e14ec98360b0a",
+
+               "00000000000000000000000000000000",
+               "ffffffff000000000000000000000000",
+               "c26277437420c5d634f715aea81a9132",
+
+               "00000000000000000000000000000000",
+               "ffffffff800000000000000000000000",
+               "171a0e1b2dd424f0e089af2c4c10f32f",
+
+               "00000000000000000000000000000000",
+               "ffffffffc00000000000000000000000",
+               "7cadbe402d1b208fe735edce00aee7ce",
+
+               "00000000000000000000000000000000",
+               "ffffffffe00000000000000000000000",
+               "43b02ff929a1485af6f5c6d6558baa0f",
+
+               "00000000000000000000000000000000",
+               "fffffffff00000000000000000000000",
+               "092faacc9bf43508bf8fa8613ca75dea",
+
+               "00000000000000000000000000000000",
+               "fffffffff80000000000000000000000",
+               "cb2bf8280f3f9742c7ed513fe802629c",
+
+               "00000000000000000000000000000000",
+               "fffffffffc0000000000000000000000",
+               "215a41ee442fa992a6e323986ded3f68",
+
+               "00000000000000000000000000000000",
+               "fffffffffe0000000000000000000000",
+               "f21e99cf4f0f77cea836e11a2fe75fb1",
+
+               "00000000000000000000000000000000",
+               "ffffffffff0000000000000000000000",
+               "95e3a0ca9079e646331df8b4e70d2cd6",
+
+               "00000000000000000000000000000000",
+               "ffffffffff8000000000000000000000",
+               "4afe7f120ce7613f74fc12a01a828073",
+
+               "00000000000000000000000000000000",
+               "ffffffffffc000000000000000000000",
+               "827f000e75e2c8b9d479beed913fe678",
+
+               "00000000000000000000000000000000",
+               "ffffffffffe000000000000000000000",
+               "35830c8e7aaefe2d30310ef381cbf691",
+
+               "00000000000000000000000000000000",
+               "fffffffffff000000000000000000000",
+               "191aa0f2c8570144f38657ea4085ebe5",
+
+               "00000000000000000000000000000000",
+               "fffffffffff800000000000000000000",
+               "85062c2c909f15d9269b6c18ce99c4f0",
+
+               "00000000000000000000000000000000",
+               "fffffffffffc00000000000000000000",
+               "678034dc9e41b5a560ed239eeab1bc78",
+
+               "00000000000000000000000000000000",
+               "fffffffffffe00000000000000000000",
+               "c2f93a4ce5ab6d5d56f1b93cf19911c1",
+
+               "00000000000000000000000000000000",
+               "ffffffffffff00000000000000000000",
+               "1c3112bcb0c1dcc749d799743691bf82",
+
+               "00000000000000000000000000000000",
+               "ffffffffffff80000000000000000000",
+               "00c55bd75c7f9c881989d3ec1911c0d4",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffc0000000000000000000",
+               "ea2e6b5ef182b7dff3629abd6a12045f",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffe0000000000000000000",
+               "22322327e01780b17397f24087f8cc6f",
+
+               "00000000000000000000000000000000",
+               "fffffffffffff0000000000000000000",
+               "c9cacb5cd11692c373b2411768149ee7",
+
+               "00000000000000000000000000000000",
+               "fffffffffffff8000000000000000000",
+               "a18e3dbbca577860dab6b80da3139256",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffc000000000000000000",
+               "79b61c37bf328ecca8d743265a3d425c",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffe000000000000000000",
+               "d2d99c6bcc1f06fda8e27e8ae3f1ccc7",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffff000000000000000000",
+               "1bfd4b91c701fd6b61b7f997829d663b",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffff800000000000000000",
+               "11005d52f25f16bdc9545a876a63490a",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffc00000000000000000",
+               "3a4d354f02bb5a5e47d39666867f246a",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffe00000000000000000",
+               "d451b8d6e1e1a0ebb155fbbf6e7b7dc3",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffff00000000000000000",
+               "6898d4f42fa7ba6a10ac05e87b9f2080",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffff80000000000000000",
+               "b611295e739ca7d9b50f8e4c0e754a3f",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffc0000000000000000",
+               "7d33fc7d8abe3ca1936759f8f5deaf20",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffe0000000000000000",
+               "3b5e0f566dc96c298f0c12637539b25c",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffff0000000000000000",
+               "f807c3e7985fe0f5a50e2cdb25c5109e",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffff8000000000000000",
+               "41f992a856fb278b389a62f5d274d7e9",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffc000000000000000",
+               "10d3ed7a6fe15ab4d91acbc7d0767ab1",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffe000000000000000",
+               "21feecd45b2e675973ac33bf0c5424fc",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffff000000000000000",
+               "1480cb3955ba62d09eea668f7c708817",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffff800000000000000",
+               "66404033d6b72b609354d5496e7eb511",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffc00000000000000",
+               "1c317a220a7d700da2b1e075b00266e1",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffe00000000000000",
+               "ab3b89542233f1271bf8fd0c0f403545",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffff00000000000000",
+               "d93eae966fac46dca927d6b114fa3f9e",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffff80000000000000",
+               "1bdec521316503d9d5ee65df3ea94ddf",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffc0000000000000",
+               "eef456431dea8b4acf83bdae3717f75f",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffe0000000000000",
+               "06f2519a2fafaa596bfef5cfa15c21b9",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffff0000000000000",
+               "251a7eac7e2fe809e4aa8d0d7012531a",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffff8000000000000",
+               "3bffc16e4c49b268a20f8d96a60b4058",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffc000000000000",
+               "e886f9281999c5bb3b3e8862e2f7c988",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffe000000000000",
+               "563bf90d61beef39f48dd625fcef1361",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffff000000000000",
+               "4d37c850644563c69fd0acd9a049325b",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffff800000000000",
+               "b87c921b91829ef3b13ca541ee1130a6",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffffc00000000000",
+               "2e65eb6b6ea383e109accce8326b0393",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffffe00000000000",
+               "9ca547f7439edc3e255c0f4d49aa8990",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffff00000000000",
+               "a5e652614c9300f37816b1f9fd0c87f9",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffff80000000000",
+               "14954f0b4697776f44494fe458d814ed",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffffc0000000000",
+               "7c8d9ab6c2761723fe42f8bb506cbcf7",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffffe0000000000",
+               "db7e1932679fdd99742aab04aa0d5a80",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffffff0000000000",
+               "4c6a1c83e568cd10f27c2d73ded19c28",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffffff8000000000",
+               "90ecbe6177e674c98de412413f7ac915",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffffffc000000000",
+               "90684a2ac55fe1ec2b8ebd5622520b73",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffffffe000000000",
+               "7472f9a7988607ca79707795991035e6",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffffff000000000",
+               "56aff089878bf3352f8df172a3ae47d8",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffffff800000000",
+               "65c0526cbe40161b8019a2a3171abd23",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffffffc00000000",
+               "377be0be33b4e3e310b4aabda173f84f",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffffffe00000000",
+               "9402e9aa6f69de6504da8d20c4fcaa2f",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffffffff00000000",
+               "123c1f4af313ad8c2ce648b2e71fb6e1",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffffffff80000000",
+               "1ffc626d30203dcdb0019fb80f726cf4",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffffffffc0000000",
+               "76da1fbe3a50728c50fd2e621b5ad885",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffffffffe0000000",
+               "082eb8be35f442fb52668e16a591d1d6",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffffffff0000000",
+               "e656f9ecf5fe27ec3e4a73d00c282fb3",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffffffff8000000",
+               "2ca8209d63274cd9a29bb74bcd77683a",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffffffffc000000",
+               "79bf5dce14bb7dd73a8e3611de7ce026",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffffffffe000000",
+               "3c849939a5d29399f344c4a0eca8a576",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffffffffff000000",
+               "ed3c0a94d59bece98835da7aa4f07ca2",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffffffffff800000",
+               "63919ed4ce10196438b6ad09d99cd795",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffffffffffc00000",
+               "7678f3a833f19fea95f3c6029e2bc610",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffffffffffe00000",
+               "3aa426831067d36b92be7c5f81c13c56",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffffffffff00000",
+               "9272e2d2cdd11050998c845077a30ea0",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffffffffff80000",
+               "088c4b53f5ec0ff814c19adae7f6246c",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffffffffffc0000",
+               "4010a5e401fdf0a0354ddbcc0d012b17",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffffffffffe0000",
+               "a87a385736c0a6189bd6589bd8445a93",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffffffffffff0000",
+               "545f2b83d9616dccf60fa9830e9cd287",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffffffffffff8000",
+               "4b706f7f92406352394037a6d4f4688d",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffffffffffffc000",
+               "b7972b3941c44b90afa7b264bfba7387",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffffffffffffe000",
+               "6f45732cf10881546f0fd23896d2bb60",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffffffffffff000",
+               "2e3579ca15af27f64b3c955a5bfc30ba",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffffffffffff800",
+               "34a2c5a91ae2aec99b7d1b5fa6780447",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffffffffffffc00",
+               "a4d6616bd04f87335b0e53351227a9ee",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffffffffffffe00",
+               "7f692b03945867d16179a8cefc83ea3f",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffffffffffffff00",
+               "3bd141ee84a0e6414a26e7a4f281f8a2",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffffffffffffff80",
+               "d1788f572d98b2b16ec5d5f3922b99bc",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffffffffffffffc0",
+               "0833ff6f61d98a57b288e8c3586b85a6",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffffffffffffffe0",
+               "8568261797de176bf0b43becc6285afb",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffffffffffffff0",
+               "f9b0fda0c4a898f5b9e6f661c4ce4d07",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffffffffffffff8",
+               "8ade895913685c67c5269f8aae42983e",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffffffffffffffc",
+               "39bde67d5c8ed8a8b1c37eb8fa9f5ac0",
+
+               "00000000000000000000000000000000",
+               "fffffffffffffffffffffffffffffffe",
+               "5c005e72c1418c44f569f2ea33ba54f3",
+
+               "00000000000000000000000000000000",
+               "ffffffffffffffffffffffffffffffff",
+               "3f5b8cc9ea855a0afa7347d23e8d664e",
+
+               /*
+                * From NIST validation suite (ECBVarTxt192.rsp).
+                */
+               "000000000000000000000000000000000000000000000000",
+               "80000000000000000000000000000000",
+               "6cd02513e8d4dc986b4afe087a60bd0c",
+
+               "000000000000000000000000000000000000000000000000",
+               "c0000000000000000000000000000000",
+               "2ce1f8b7e30627c1c4519eada44bc436",
+
+               "000000000000000000000000000000000000000000000000",
+               "e0000000000000000000000000000000",
+               "9946b5f87af446f5796c1fee63a2da24",
+
+               "000000000000000000000000000000000000000000000000",
+               "f0000000000000000000000000000000",
+               "2a560364ce529efc21788779568d5555",
+
+               "000000000000000000000000000000000000000000000000",
+               "f8000000000000000000000000000000",
+               "35c1471837af446153bce55d5ba72a0a",
+
+               "000000000000000000000000000000000000000000000000",
+               "fc000000000000000000000000000000",
+               "ce60bc52386234f158f84341e534cd9e",
+
+               "000000000000000000000000000000000000000000000000",
+               "fe000000000000000000000000000000",
+               "8c7c27ff32bcf8dc2dc57c90c2903961",
+
+               "000000000000000000000000000000000000000000000000",
+               "ff000000000000000000000000000000",
+               "32bb6a7ec84499e166f936003d55a5bb",
+
+               "000000000000000000000000000000000000000000000000",
+               "ff800000000000000000000000000000",
+               "a5c772e5c62631ef660ee1d5877f6d1b",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffc00000000000000000000000000000",
+               "030d7e5b64f380a7e4ea5387b5cd7f49",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffe00000000000000000000000000000",
+               "0dc9a2610037009b698f11bb7e86c83e",
+
+               "000000000000000000000000000000000000000000000000",
+               "fff00000000000000000000000000000",
+               "0046612c766d1840c226364f1fa7ed72",
+
+               "000000000000000000000000000000000000000000000000",
+               "fff80000000000000000000000000000",
+               "4880c7e08f27befe78590743c05e698b",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffc0000000000000000000000000000",
+               "2520ce829a26577f0f4822c4ecc87401",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffe0000000000000000000000000000",
+               "8765e8acc169758319cb46dc7bcf3dca",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffff0000000000000000000000000000",
+               "e98f4ba4f073df4baa116d011dc24a28",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffff8000000000000000000000000000",
+               "f378f68c5dbf59e211b3a659a7317d94",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffc000000000000000000000000000",
+               "283d3b069d8eb9fb432d74b96ca762b4",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffe000000000000000000000000000",
+               "a7e1842e8a87861c221a500883245c51",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffff000000000000000000000000000",
+               "77aa270471881be070fb52c7067ce732",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffff800000000000000000000000000",
+               "01b0f476d484f43f1aeb6efa9361a8ac",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffc00000000000000000000000000",
+               "1c3a94f1c052c55c2d8359aff2163b4f",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffe00000000000000000000000000",
+               "e8a067b604d5373d8b0f2e05a03b341b",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffff00000000000000000000000000",
+               "a7876ec87f5a09bfea42c77da30fd50e",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffff80000000000000000000000000",
+               "0cf3e9d3a42be5b854ca65b13f35f48d",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffc0000000000000000000000000",
+               "6c62f6bbcab7c3e821c9290f08892dda",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffe0000000000000000000000000",
+               "7f5e05bd2068738196fee79ace7e3aec",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffff0000000000000000000000000",
+               "440e0d733255cda92fb46e842fe58054",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffff8000000000000000000000000",
+               "aa5d5b1c4ea1b7a22e5583ac2e9ed8a7",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffc000000000000000000000000",
+               "77e537e89e8491e8662aae3bc809421d",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffe000000000000000000000000",
+               "997dd3e9f1598bfa73f75973f7e93b76",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffff000000000000000000000000",
+               "1b38d4f7452afefcb7fc721244e4b72e",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffff800000000000000000000000",
+               "0be2b18252e774dda30cdda02c6906e3",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffc00000000000000000000000",
+               "d2695e59c20361d82652d7d58b6f11b2",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffe00000000000000000000000",
+               "902d88d13eae52089abd6143cfe394e9",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffff00000000000000000000000",
+               "d49bceb3b823fedd602c305345734bd2",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffff80000000000000000000000",
+               "707b1dbb0ffa40ef7d95def421233fae",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffc0000000000000000000000",
+               "7ca0c1d93356d9eb8aa952084d75f913",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffe0000000000000000000000",
+               "f2cbf9cb186e270dd7bdb0c28febc57d",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffff0000000000000000000000",
+               "c94337c37c4e790ab45780bd9c3674a0",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffff8000000000000000000000",
+               "8e3558c135252fb9c9f367ed609467a1",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffc000000000000000000000",
+               "1b72eeaee4899b443914e5b3a57fba92",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffe000000000000000000000",
+               "011865f91bc56868d051e52c9efd59b7",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffff000000000000000000000",
+               "e4771318ad7a63dd680f6e583b7747ea",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffff800000000000000000000",
+               "61e3d194088dc8d97e9e6db37457eac5",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffc00000000000000000000",
+               "36ff1ec9ccfbc349e5d356d063693ad6",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffe00000000000000000000",
+               "3cc9e9a9be8cc3f6fb2ea24088e9bb19",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffff00000000000000000000",
+               "1ee5ab003dc8722e74905d9a8fe3d350",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffff80000000000000000000",
+               "245339319584b0a412412869d6c2eada",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffc0000000000000000000",
+               "7bd496918115d14ed5380852716c8814",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffe0000000000000000000",
+               "273ab2f2b4a366a57d582a339313c8b1",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffff0000000000000000000",
+               "113365a9ffbe3b0ca61e98507554168b",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffff8000000000000000000",
+               "afa99c997ac478a0dea4119c9e45f8b1",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffc000000000000000000",
+               "9216309a7842430b83ffb98638011512",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffe000000000000000000",
+               "62abc792288258492a7cb45145f4b759",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffff000000000000000000",
+               "534923c169d504d7519c15d30e756c50",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffff800000000000000000",
+               "fa75e05bcdc7e00c273fa33f6ee441d2",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffc00000000000000000",
+               "7d350fa6057080f1086a56b17ec240db",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffe00000000000000000",
+               "f34e4a6324ea4a5c39a661c8fe5ada8f",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffff00000000000000000",
+               "0882a16f44088d42447a29ac090ec17e",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffff80000000000000000",
+               "3a3c15bfc11a9537c130687004e136ee",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffc0000000000000000",
+               "22c0a7678dc6d8cf5c8a6d5a9960767c",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffe0000000000000000",
+               "b46b09809d68b9a456432a79bdc2e38c",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffff0000000000000000",
+               "93baaffb35fbe739c17c6ac22eecf18f",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffff8000000000000000",
+               "c8aa80a7850675bc007c46df06b49868",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffc000000000000000",
+               "12c6f3877af421a918a84b775858021d",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffe000000000000000",
+               "33f123282c5d633924f7d5ba3f3cab11",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffff000000000000000",
+               "a8f161002733e93ca4527d22c1a0c5bb",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffff800000000000000",
+               "b72f70ebf3e3fda23f508eec76b42c02",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffc00000000000000",
+               "6a9d965e6274143f25afdcfc88ffd77c",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffe00000000000000",
+               "a0c74fd0b9361764ce91c5200b095357",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffff00000000000000",
+               "091d1fdc2bd2c346cd5046a8c6209146",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffff80000000000000",
+               "e2a37580116cfb71856254496ab0aca8",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffc0000000000000",
+               "e0b3a00785917c7efc9adba322813571",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffe0000000000000",
+               "733d41f4727b5ef0df4af4cf3cffa0cb",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffff0000000000000",
+               "a99ebb030260826f981ad3e64490aa4f",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffff8000000000000",
+               "73f34c7d3eae5e80082c1647524308ee",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffc000000000000",
+               "40ebd5ad082345b7a2097ccd3464da02",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffe000000000000",
+               "7cc4ae9a424b2cec90c97153c2457ec5",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffff000000000000",
+               "54d632d03aba0bd0f91877ebdd4d09cb",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffff800000000000",
+               "d3427be7e4d27cd54f5fe37b03cf0897",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffc00000000000",
+               "b2099795e88cc158fd75ea133d7e7fbe",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffe00000000000",
+               "a6cae46fb6fadfe7a2c302a34242817b",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffff00000000000",
+               "026a7024d6a902e0b3ffccbaa910cc3f",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffff80000000000",
+               "156f07767a85a4312321f63968338a01",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffc0000000000",
+               "15eec9ebf42b9ca76897d2cd6c5a12e2",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffe0000000000",
+               "db0d3a6fdcc13f915e2b302ceeb70fd8",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffff0000000000",
+               "71dbf37e87a2e34d15b20e8f10e48924",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffff8000000000",
+               "c745c451e96ff3c045e4367c833e3b54",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffc000000000",
+               "340da09c2dd11c3b679d08ccd27dd595",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffe000000000",
+               "8279f7c0c2a03ee660c6d392db025d18",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffff000000000",
+               "a4b2c7d8eba531ff47c5041a55fbd1ec",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffff800000000",
+               "74569a2ca5a7bd5131ce8dc7cbfbf72f",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffc00000000",
+               "3713da0c0219b63454035613b5a403dd",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffe00000000",
+               "8827551ddcc9df23fa72a3de4e9f0b07",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffff00000000",
+               "2e3febfd625bfcd0a2c06eb460da1732",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffff80000000",
+               "ee82e6ba488156f76496311da6941deb",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffc0000000",
+               "4770446f01d1f391256e85a1b30d89d3",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffe0000000",
+               "af04b68f104f21ef2afb4767cf74143c",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffff0000000",
+               "cf3579a9ba38c8e43653173e14f3a4c6",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffff8000000",
+               "b3bba904f4953e09b54800af2f62e7d4",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffc000000",
+               "fc4249656e14b29eb9c44829b4c59a46",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffe000000",
+               "9b31568febe81cfc2e65af1c86d1a308",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffff000000",
+               "9ca09c25f273a766db98a480ce8dfedc",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffff800000",
+               "b909925786f34c3c92d971883c9fbedf",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffffc00000",
+               "82647f1332fe570a9d4d92b2ee771d3b",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffffe00000",
+               "3604a7e80832b3a99954bca6f5b9f501",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffff00000",
+               "884607b128c5de3ab39a529a1ef51bef",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffff80000",
+               "670cfa093d1dbdb2317041404102435e",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffffc0000",
+               "7a867195f3ce8769cbd336502fbb5130",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffffe0000",
+               "52efcf64c72b2f7ca5b3c836b1078c15",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffffff0000",
+               "4019250f6eefb2ac5ccbcae044e75c7e",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffffff8000",
+               "022c4f6f5a017d292785627667ddef24",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffffffc000",
+               "e9c21078a2eb7e03250f71000fa9e3ed",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffffffe000",
+               "a13eaeeb9cd391da4e2b09490b3e7fad",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffffff000",
+               "c958a171dca1d4ed53e1af1d380803a9",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffffff800",
+               "21442e07a110667f2583eaeeee44dc8c",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffffffc00",
+               "59bbb353cf1dd867a6e33737af655e99",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffffffe00",
+               "43cd3b25375d0ce41087ff9fe2829639",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffffffff00",
+               "6b98b17e80d1118e3516bd768b285a84",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffffffff80",
+               "ae47ed3676ca0c08deea02d95b81db58",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffffffffc0",
+               "34ec40dc20413795ed53628ea748720b",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffffffffe0",
+               "4dc68163f8e9835473253542c8a65d46",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffffffff0",
+               "2aabb999f43693175af65c6c612c46fb",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffffffff8",
+               "e01f94499dac3547515c5b1d756f0f58",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffffffffc",
+               "9d12435a46480ce00ea349f71799df9a",
+
+               "000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffffffffe",
+               "cef41d16d266bdfe46938ad7884cc0cf",
+
+               "000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffffffffff",
+               "b13db4da1f718bc6904797c82bcf2d32",
+
+               /*
+                * From NIST validation suite (ECBVarTxt256.rsp).
+                */
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "80000000000000000000000000000000",
+               "ddc6bf790c15760d8d9aeb6f9a75fd4e",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "c0000000000000000000000000000000",
+               "0a6bdc6d4c1e6280301fd8e97ddbe601",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "e0000000000000000000000000000000",
+               "9b80eefb7ebe2d2b16247aa0efc72f5d",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "f0000000000000000000000000000000",
+               "7f2c5ece07a98d8bee13c51177395ff7",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "f8000000000000000000000000000000",
+               "7818d800dcf6f4be1e0e94f403d1e4c2",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fc000000000000000000000000000000",
+               "e74cd1c92f0919c35a0324123d6177d3",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fe000000000000000000000000000000",
+               "8092a4dcf2da7e77e93bdd371dfed82e",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ff000000000000000000000000000000",
+               "49af6b372135acef10132e548f217b17",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ff800000000000000000000000000000",
+               "8bcd40f94ebb63b9f7909676e667f1e7",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffc00000000000000000000000000000",
+               "fe1cffb83f45dcfb38b29be438dbd3ab",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffe00000000000000000000000000000",
+               "0dc58a8d886623705aec15cb1e70dc0e",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fff00000000000000000000000000000",
+               "c218faa16056bd0774c3e8d79c35a5e4",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fff80000000000000000000000000000",
+               "047bba83f7aa841731504e012208fc9e",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffc0000000000000000000000000000",
+               "dc8f0e4915fd81ba70a331310882f6da",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffe0000000000000000000000000000",
+               "1569859ea6b7206c30bf4fd0cbfac33c",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffff0000000000000000000000000000",
+               "300ade92f88f48fa2df730ec16ef44cd",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffff8000000000000000000000000000",
+               "1fe6cc3c05965dc08eb0590c95ac71d0",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffc000000000000000000000000000",
+               "59e858eaaa97fec38111275b6cf5abc0",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffe000000000000000000000000000",
+               "2239455e7afe3b0616100288cc5a723b",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffff000000000000000000000000000",
+               "3ee500c5c8d63479717163e55c5c4522",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffff800000000000000000000000000",
+               "d5e38bf15f16d90e3e214041d774daa8",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffc00000000000000000000000000",
+               "b1f4066e6f4f187dfe5f2ad1b17819d0",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffe00000000000000000000000000",
+               "6ef4cc4de49b11065d7af2909854794a",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffff00000000000000000000000000",
+               "ac86bc606b6640c309e782f232bf367f",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffff80000000000000000000000000",
+               "36aff0ef7bf3280772cf4cac80a0d2b2",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffc0000000000000000000000000",
+               "1f8eedea0f62a1406d58cfc3ecea72cf",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffe0000000000000000000000000",
+               "abf4154a3375a1d3e6b1d454438f95a6",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffff0000000000000000000000000",
+               "96f96e9d607f6615fc192061ee648b07",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffff8000000000000000000000000",
+               "cf37cdaaa0d2d536c71857634c792064",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffc000000000000000000000000",
+               "fbd6640c80245c2b805373f130703127",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffe000000000000000000000000",
+               "8d6a8afe55a6e481badae0d146f436db",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffff000000000000000000000000",
+               "6a4981f2915e3e68af6c22385dd06756",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffff800000000000000000000000",
+               "42a1136e5f8d8d21d3101998642d573b",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffc00000000000000000000000",
+               "9b471596dc69ae1586cee6158b0b0181",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffe00000000000000000000000",
+               "753665c4af1eff33aa8b628bf8741cfd",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffff00000000000000000000000",
+               "9a682acf40be01f5b2a4193c9a82404d",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffff80000000000000000000000",
+               "54fafe26e4287f17d1935f87eb9ade01",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffc0000000000000000000000",
+               "49d541b2e74cfe73e6a8e8225f7bd449",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffe0000000000000000000000",
+               "11a45530f624ff6f76a1b3826626ff7b",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffff0000000000000000000000",
+               "f96b0c4a8bc6c86130289f60b43b8fba",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffff8000000000000000000000",
+               "48c7d0e80834ebdc35b6735f76b46c8b",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffc000000000000000000000",
+               "2463531ab54d66955e73edc4cb8eaa45",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffe000000000000000000000",
+               "ac9bd8e2530469134b9d5b065d4f565b",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffff000000000000000000000",
+               "3f5f9106d0e52f973d4890e6f37e8a00",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffff800000000000000000000",
+               "20ebc86f1304d272e2e207e59db639f0",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffc00000000000000000000",
+               "e67ae6426bf9526c972cff072b52252c",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffe00000000000000000000",
+               "1a518dddaf9efa0d002cc58d107edfc8",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffff00000000000000000000",
+               "ead731af4d3a2fe3b34bed047942a49f",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffff80000000000000000000",
+               "b1d4efe40242f83e93b6c8d7efb5eae9",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffc0000000000000000000",
+               "cd2b1fec11fd906c5c7630099443610a",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffe0000000000000000000",
+               "a1853fe47fe29289d153161d06387d21",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffff0000000000000000000",
+               "4632154179a555c17ea604d0889fab14",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffff8000000000000000000",
+               "dd27cac6401a022e8f38f9f93e774417",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffc000000000000000000",
+               "c090313eb98674f35f3123385fb95d4d",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffe000000000000000000",
+               "cc3526262b92f02edce548f716b9f45c",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffff000000000000000000",
+               "c0838d1a2b16a7c7f0dfcc433c399c33",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffff800000000000000000",
+               "0d9ac756eb297695eed4d382eb126d26",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffc00000000000000000",
+               "56ede9dda3f6f141bff1757fa689c3e1",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffe00000000000000000",
+               "768f520efe0f23e61d3ec8ad9ce91774",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffff00000000000000000",
+               "b1144ddfa75755213390e7c596660490",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffff80000000000000000",
+               "1d7c0c4040b355b9d107a99325e3b050",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffc0000000000000000",
+               "d8e2bb1ae8ee3dcf5bf7d6c38da82a1a",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffe0000000000000000",
+               "faf82d178af25a9886a47e7f789b98d7",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffff0000000000000000",
+               "9b58dbfd77fe5aca9cfc190cd1b82d19",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffff8000000000000000",
+               "77f392089042e478ac16c0c86a0b5db5",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffc000000000000000",
+               "19f08e3420ee69b477ca1420281c4782",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffe000000000000000",
+               "a1b19beee4e117139f74b3c53fdcb875",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffff000000000000000",
+               "a37a5869b218a9f3a0868d19aea0ad6a",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffff800000000000000",
+               "bc3594e865bcd0261b13202731f33580",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffc00000000000000",
+               "811441ce1d309eee7185e8c752c07557",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffe00000000000000",
+               "959971ce4134190563518e700b9874d1",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffff00000000000000",
+               "76b5614a042707c98e2132e2e805fe63",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffff80000000000000",
+               "7d9fa6a57530d0f036fec31c230b0cc6",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffc0000000000000",
+               "964153a83bf6989a4ba80daa91c3e081",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffe0000000000000",
+               "a013014d4ce8054cf2591d06f6f2f176",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffff0000000000000",
+               "d1c5f6399bf382502e385eee1474a869",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffff8000000000000",
+               "0007e20b8298ec354f0f5fe7470f36bd",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffc000000000000",
+               "b95ba05b332da61ef63a2b31fcad9879",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffe000000000000",
+               "4620a49bd967491561669ab25dce45f4",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffff000000000000",
+               "12e71214ae8e04f0bb63d7425c6f14d5",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffff800000000000",
+               "4cc42fc1407b008fe350907c092e80ac",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffc00000000000",
+               "08b244ce7cbc8ee97fbba808cb146fda",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffe00000000000",
+               "39b333e8694f21546ad1edd9d87ed95b",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffff00000000000",
+               "3b271f8ab2e6e4a20ba8090f43ba78f3",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffff80000000000",
+               "9ad983f3bf651cd0393f0a73cccdea50",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffc0000000000",
+               "8f476cbff75c1f725ce18e4bbcd19b32",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffe0000000000",
+               "905b6267f1d6ab5320835a133f096f2a",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffff0000000000",
+               "145b60d6d0193c23f4221848a892d61a",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffff8000000000",
+               "55cfb3fb6d75cad0445bbc8dafa25b0f",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffc000000000",
+               "7b8e7098e357ef71237d46d8b075b0f5",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffe000000000",
+               "2bf27229901eb40f2df9d8398d1505ae",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffff000000000",
+               "83a63402a77f9ad5c1e931a931ecd706",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffff800000000",
+               "6f8ba6521152d31f2bada1843e26b973",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffc00000000",
+               "e5c3b8e30fd2d8e6239b17b44bd23bbd",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffe00000000",
+               "1ac1f7102c59933e8b2ddc3f14e94baa",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffff00000000",
+               "21d9ba49f276b45f11af8fc71a088e3d",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffff80000000",
+               "649f1cddc3792b4638635a392bc9bade",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffc0000000",
+               "e2775e4b59c1bc2e31a2078c11b5a08c",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffe0000000",
+               "2be1fae5048a25582a679ca10905eb80",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffff0000000",
+               "da86f292c6f41ea34fb2068df75ecc29",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffff8000000",
+               "220df19f85d69b1b562fa69a3c5beca5",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffc000000",
+               "1f11d5d0355e0b556ccdb6c7f5083b4d",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffe000000",
+               "62526b78be79cb384633c91f83b4151b",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffff000000",
+               "90ddbcb950843592dd47bbef00fdc876",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffff800000",
+               "2fd0e41c5b8402277354a7391d2618e2",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffffc00000",
+               "3cdf13e72dee4c581bafec70b85f9660",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffffe00000",
+               "afa2ffc137577092e2b654fa199d2c43",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffff00000",
+               "8d683ee63e60d208e343ce48dbc44cac",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffff80000",
+               "705a4ef8ba2133729c20185c3d3a4763",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffffc0000",
+               "0861a861c3db4e94194211b77ed761b9",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffffe0000",
+               "4b00c27e8b26da7eab9d3a88dec8b031",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffffff0000",
+               "5f397bf03084820cc8810d52e5b666e9",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffffff8000",
+               "63fafabb72c07bfbd3ddc9b1203104b8",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffffffc000",
+               "683e2140585b18452dd4ffbb93c95df9",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffffffe000",
+               "286894e48e537f8763b56707d7d155c8",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffffff000",
+               "a423deabc173dcf7e2c4c53e77d37cd1",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffffff800",
+               "eb8168313e1cfdfdb5e986d5429cf172",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffffffc00",
+               "27127daafc9accd2fb334ec3eba52323",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffffffe00",
+               "ee0715b96f72e3f7a22a5064fc592f4c",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffffffff00",
+               "29ee526770f2a11dcfa989d1ce88830f",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffffffff80",
+               "0493370e054b09871130fe49af730a5a",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffffffffc0",
+               "9b7b940f6c509f9e44a4ee140448ee46",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffffffffe0",
+               "2915be4a1ecfdcbe3e023811a12bb6c7",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffffffff0",
+               "7240e524bc51d8c4d440b1be55d1062c",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffffffff8",
+               "da63039d38cb4612b2dc36ba26684b93",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffffffffc",
+               "0f59cb5a4b522e2ac56c1a64f558ad9a",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "fffffffffffffffffffffffffffffffe",
+               "7bfe9d876c6d63c1d035da8fe21c409d",
+
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "ffffffffffffffffffffffffffffffff",
+               "acdace8078a32b1a182bfa4987ca1347"
+       };
+
+       /*
+        * AES known-answer tests for CBC.
+        * Order: key, IV, plaintext, ciphertext.
+        */
+       static string[] KAT_AES_CBC = {
+               /*
+                * From NIST validation suite "Multiblock Message Test"
+                * (cbcmmt128.rsp).
+                */
+               "1f8e4973953f3fb0bd6b16662e9a3c17",
+               "2fe2b333ceda8f98f4a99b40d2cd34a8",
+               "45cf12964fc824ab76616ae2f4bf0822",
+               "0f61c4d44c5147c03c195ad7e2cc12b2",
+
+               "0700d603a1c514e46b6191ba430a3a0c",
+               "aad1583cd91365e3bb2f0c3430d065bb",
+               "068b25c7bfb1f8bdd4cfc908f69dffc5ddc726a197f0e5f720f730393279be91",
+               "c4dc61d9725967a3020104a9738f23868527ce839aab1752fd8bdb95a82c4d00",
+
+               "3348aa51e9a45c2dbe33ccc47f96e8de",
+               "19153c673160df2b1d38c28060e59b96",
+               "9b7cee827a26575afdbb7c7a329f887238052e3601a7917456ba61251c214763d5e1847a6ad5d54127a399ab07ee3599",
+               "d5aed6c9622ec451a15db12819952b6752501cf05cdbf8cda34a457726ded97818e1f127a28d72db5652749f0c6afee5",
+
+               "b7f3c9576e12dd0db63e8f8fac2b9a39",
+               "c80f095d8bb1a060699f7c19974a1aa0",
+               "9ac19954ce1319b354d3220460f71c1e373f1cd336240881160cfde46ebfed2e791e8d5a1a136ebd1dc469dec00c4187722b841cdabcb22c1be8a14657da200e",
+               "19b9609772c63f338608bf6eb52ca10be65097f89c1e0905c42401fd47791ae2c5440b2d473116ca78bd9ff2fb6015cfd316524eae7dcb95ae738ebeae84a467",
+
+               "b6f9afbfe5a1562bba1368fc72ac9d9c",
+               "3f9d5ebe250ee7ce384b0d00ee849322",
+               "db397ec22718dbffb9c9d13de0efcd4611bf792be4fce0dc5f25d4f577ed8cdbd4eb9208d593dda3d4653954ab64f05676caa3ce9bfa795b08b67ceebc923fdc89a8c431188e9e482d8553982cf304d1",
+               "10ea27b19e16b93af169c4a88e06e35c99d8b420980b058e34b4b8f132b13766f72728202b089f428fecdb41c79f8aa0d0ef68f5786481cca29e2126f69bc14160f1ae2187878ba5c49cf3961e1b7ee9",
+
+               "bbe7b7ba07124ff1ae7c3416fe8b465e",
+               "7f65b5ee3630bed6b84202d97fb97a1e",
+               "2aad0c2c4306568bad7447460fd3dac054346d26feddbc9abd9110914011b4794be2a9a00a519a51a5b5124014f4ed2735480db21b434e99a911bb0b60fe0253763725b628d5739a5117b7ee3aefafc5b4c1bf446467e7bf5f78f31ff7caf187",
+               "3b8611bfc4973c5cd8e982b073b33184cd26110159172e44988eb5ff5661a1e16fad67258fcbfee55469267a12dc374893b4e3533d36f5634c3095583596f135aa8cd1138dc898bc5651ee35a92ebf89ab6aeb5366653bc60a70e0074fc11efe",
+
+               "89a553730433f7e6d67d16d373bd5360",
+               "f724558db3433a523f4e51a5bea70497",
+               "807bc4ea684eedcfdcca30180680b0f1ae2814f35f36d053c5aea6595a386c1442770f4d7297d8b91825ee7237241da8925dd594ccf676aecd46ca2068e8d37a3a0ec8a7d5185a201e663b5ff36ae197110188a23503763b8218826d23ced74b31e9f6e2d7fbfa6cb43420c7807a8625",
+               "406af1429a478c3d07e555c5287a60500d37fc39b68e5bbb9bafd6ddb223828561d6171a308d5b1a4551e8a5e7d572918d25c968d3871848d2f16635caa9847f38590b1df58ab5efb985f2c66cfaf86f61b3f9c0afad6c963c49cee9b8bc81a2ddb06c967f325515a4849eec37ce721a",
+
+               "c491ca31f91708458e29a925ec558d78",
+               "9ef934946e5cd0ae97bd58532cb49381",
+               "cb6a787e0dec56f9a165957f81af336ca6b40785d9e94093c6190e5152649f882e874d79ac5e167bd2a74ce5ae088d2ee854f6539e0a94796b1e1bd4c9fcdbc79acbef4d01eeb89776d18af71ae2a4fc47dd66df6c4dbe1d1850e466549a47b636bcc7c2b3a62495b56bb67b6d455f1eebd9bfefecbca6c7f335cfce9b45cb9d",
+               "7b2931f5855f717145e00f152a9f4794359b1ffcb3e55f594e33098b51c23a6c74a06c1d94fded7fd2ae42c7db7acaef5844cb33aeddc6852585ed0020a6699d2cb53809cefd169148ce42292afab063443978306c582c18b9ce0da3d084ce4d3c482cfd8fcf1a85084e89fb88b40a084d5e972466d07666126fb761f84078f2",
+
+               "f6e87d71b0104d6eb06a68dc6a71f498",
+               "1c245f26195b76ebebc2edcac412a2f8",
+               "f82bef3c73a6f7f80db285726d691db6bf55eec25a859d3ba0e0445f26b9bb3b16a3161ed1866e4dd8f2e5f8ecb4e46d74a7a78c20cdfc7bcc9e479ba7a0caba9438238ad0c01651d5d98de37f03ddce6e6b4bd4ab03cf9e8ed818aedfa1cf963b932067b97d776dce1087196e7e913f7448e38244509f0caf36bd8217e15336d35c149fd4e41707893fdb84014f8729",
+               "b09512f3eff9ed0d85890983a73dadbb7c3678d52581be64a8a8fc586f490f2521297a478a0598040ebd0f5509fafb0969f9d9e600eaef33b1b93eed99687b167f89a5065aac439ce46f3b8d22d30865e64e45ef8cd30b6984353a844a11c8cd60dba0e8866b3ee30d24b3fa8a643b328353e06010fa8273c8fd54ef0a2b6930e5520aae5cd5902f9b86a33592ca4365",
+
+               "2c14413751c31e2730570ba3361c786b",
+               "1dbbeb2f19abb448af849796244a19d7",
+               "40d930f9a05334d9816fe204999c3f82a03f6a0457a8c475c94553d1d116693adc618049f0a769a2eed6a6cb14c0143ec5cccdbc8dec4ce560cfd206225709326d4de7948e54d603d01b12d7fed752fb23f1aa4494fbb00130e9ded4e77e37c079042d828040c325b1a5efd15fc842e44014ca4374bf38f3c3fc3ee327733b0c8aee1abcd055772f18dc04603f7b2c1ea69ff662361f2be0a171bbdcea1e5d3f",
+               "6be8a12800455a320538853e0cba31bd2d80ea0c85164a4c5c261ae485417d93effe2ebc0d0a0b51d6ea18633d210cf63c0c4ddbc27607f2e81ed9113191ef86d56f3b99be6c415a4150299fb846ce7160b40b63baf1179d19275a2e83698376d28b92548c68e06e6d994e2c1501ed297014e702cdefee2f656447706009614d801de1caaf73f8b7fa56cf1ba94b631933bbe577624380850f117435a0355b2b",
+
+               /*
+                * From NIST validation suite "Multiblock Message Test"
+                * (cbcmmt192.rsp).
+                */
+               "ba75f4d1d9d7cf7f551445d56cc1a8ab2a078e15e049dc2c",
+               "531ce78176401666aa30db94ec4a30eb",
+               "c51fc276774dad94bcdc1d2891ec8668",
+               "70dd95a14ee975e239df36ff4aee1d5d",
+
+               "eab3b19c581aa873e1981c83ab8d83bbf8025111fb2e6b21",
+               "f3d6667e8d4d791e60f7505ba383eb05",
+               "9d4e4cccd1682321856df069e3f1c6fa391a083a9fb02d59db74c14081b3acc4",
+               "51d44779f90d40a80048276c035cb49ca2a47bcb9b9cf7270b9144793787d53f",
+
+               "16c93bb398f1fc0cf6d68fc7a5673cdf431fa147852b4a2d",
+               "eaaeca2e07ddedf562f94df63f0a650f",
+               "c5ce958613bf741718c17444484ebaf1050ddcacb59b9590178cbe69d7ad7919608cb03af13bbe04f3506b718a301ea0",
+               "ed6a50e0c6921d52d6647f75d67b4fd56ace1fedb8b5a6a997b4d131640547d22c5d884a75e6752b5846b5b33a5181f4",
+
+               "067bb17b4df785697eaccf961f98e212cb75e6797ce935cb",
+               "8b59c9209c529ca8391c9fc0ce033c38",
+               "db3785a889b4bd387754da222f0e4c2d2bfe0d79e05bc910fba941beea30f1239eacf0068f4619ec01c368e986fca6b7c58e490579d29611bd10087986eff54f",
+               "d5f5589760bf9c762228fde236de1fa2dd2dad448db3fa9be0c4196efd46a35c84dd1ac77d9db58c95918cb317a6430a08d2fb6a8e8b0f1c9b72c7a344dc349f",
+
+               "0fd39de83e0be77a79c8a4a612e3dd9c8aae2ce35e7a2bf8",
+               "7e1d629b84f93b079be51f9a5f5cb23c",
+               "38fbda37e28fa86d9d83a4345e419dea95d28c7818ff25925db6ac3aedaf0a86154e20a4dfcc5b1b4192895393e5eb5846c88bdbd41ecf7af3104f410eaee470f5d9017ed460475f626953035a13db1f",
+               "edadae2f9a45ff3473e02d904c94d94a30a4d92da4deb6bcb4b0774472694571842039f21c496ef93fd658842c735f8a81fcd0aa578442ab893b18f606aed1bab11f81452dd45e9b56adf2eccf4ea095",
+
+               "e3fecc75f0075a09b383dfd389a3d33cc9b854b3b254c0f4",
+               "36eab883afef936cc38f63284619cd19",
+               "931b2f5f3a5820d53a6beaaa6431083a3488f4eb03b0f5b57ef838e1579623103bd6e6800377538b2e51ef708f3c4956432e8a8ee6a34e190642b26ad8bdae6c2af9a6c7996f3b6004d2671e41f1c9f40ee03d1c4a52b0a0654a331f15f34dce",
+               "75395974bd32b3665654a6c8e396b88ae34b123575872a7ab687d8e76b46df911a8a590cd01d2f5c330be3a6626e9dd3aa5e10ed14e8ff829811b6fed50f3f533ca4385a1cbca78f5c4744e50f2f8359165c2485d1324e76c3eae76a0ccac629",
+
+               "f9c27565eb07947c8cb51b79248430f7b1066c3d2fdc3d13",
+               "2bd67cc89ab7948d644a49672843cbd9",
+               "6abcc270173cf114d44847e911a050db57ba7a2e2c161c6f37ccb6aaa4677bddcaf50cad0b5f8758fcf7c0ebc650ceb5cd52cafb8f8dd3edcece55d9f1f08b9fa8f54365cf56e28b9596a7e1dd1d3418e4444a7724add4cf79d527b183ec88de4be4eeff29c80a97e54f85351cb189ee",
+               "ca282924a61187feb40520979106e5cc861957f23828dcb7285e0eaac8a0ca2a6b60503d63d6039f4693dba32fa1f73ae2e709ca94911f28a5edd1f30eaddd54680c43acc9c74cd90d8bb648b4e544275f47e514daa20697f66c738eb30337f017fca1a26da4d1a0cc0a0e98e2463070",
+
+               "fb09cf9e00dbf883689d079c920077c0073c31890b55bab5",
+               "e3c89bd097c3abddf64f4881db6dbfe2",
+               "c1a37683fb289467dd1b2c89efba16bbd2ee24cf18d19d44596ded2682c79a2f711c7a32bf6a24badd32a4ee637c73b7a41da6258635650f91fb9ffa45bdfc3cb122136241b3deced8996aa51ea8d3e81c9d70e006a44bc0571ed48623a0d622a93fa9da290baaedf5d9e876c94620945ff8ecc83f27379ed55cf490c5790f27",
+               "8158e21420f25b59d6ae943fa1cbf21f02e979f419dab0126a721b7eef55bee9ad97f5ccff7d239057bbc19a8c378142f7672f1d5e7e17d7bebcb0070e8355cace6660171a53b61816ae824a6ef69ce470b6ffd3b5bb4b438874d91d27854d3b6f25860d3868958de3307d62b1339bdddb8a318c0ce0f33c17caf0e9f6040820",
+
+               "bca6fa3c67fd294e958f66fe8bd64f45f428f5bc8e9733a7",
+               "92a47f2833f1450d1da41717bdc6e83c",
+               "5becbc31d8bead6d36ae014a5863d14a431e6b55d29ea6baaa417271716db3a33b2e506b452086dfe690834ac2de30bc41254ec5401ec47d064237c7792fdcd7914d8af20eb114756642d519021a8c75a92f6bc53d326ae9a5b7e1b10a9756574692934d9939fc399e0c203f7edf8e7e6482eadd31a0400770e897b48c6bca2b404593045080e93377358c42a0f4dede",
+               "926db248cc1ba20f0c57631a7c8aef094f791937b905949e3460240e8bfa6fa483115a1b310b6e4369caebc5262888377b1ddaa5800ea496a2bdff0f9a1031e7129c9a20e35621e7f0b8baca0d87030f2ae7ca8593c8599677a06fd4b26009ead08fecac24caa9cf2cad3b470c8227415a7b1e0f2eab3fad96d70a209c8bb26c627677e2531b9435ca6e3c444d195b5f",
+
+               "162ad50ee64a0702aa551f571dedc16b2c1b6a1e4d4b5eee",
+               "24408038161a2ccae07b029bb66355c1",
+               "be8abf00901363987a82cc77d0ec91697ba3857f9e4f84bd79406c138d02698f003276d0449120bef4578d78fecabe8e070e11710b3f0a2744bd52434ec70015884c181ebdfd51c604a71c52e4c0e110bc408cd462b248a80b8a8ac06bb952ac1d7faed144807f1a731b7febcaf7835762defe92eccfc7a9944e1c702cffe6bc86733ed321423121085ac02df8962bcbc1937092eebf0e90a8b20e3dd8c244ae",
+               "c82cf2c476dea8cb6a6e607a40d2f0391be82ea9ec84a537a6820f9afb997b76397d005424faa6a74dc4e8c7aa4a8900690f894b6d1dca80675393d2243adac762f159301e357e98b724762310cd5a7bafe1c2a030dba46fd93a9fdb89cc132ca9c17dc72031ec6822ee5a9d99dbca66c784c01b0885cbb62e29d97801927ec415a5d215158d325f9ee689437ad1b7684ad33c0d92739451ac87f39ff8c31b84",
+
+               /*
+                * From NIST validation suite "Multiblock Message Test"
+                * (cbcmmt256.rsp).
+                */
+               "6ed76d2d97c69fd1339589523931f2a6cff554b15f738f21ec72dd97a7330907",
+               "851e8764776e6796aab722dbb644ace8",
+               "6282b8c05c5c1530b97d4816ca434762",
+               "6acc04142e100a65f51b97adf5172c41",
+
+               "dce26c6b4cfb286510da4eecd2cffe6cdf430f33db9b5f77b460679bd49d13ae",
+               "fdeaa134c8d7379d457175fd1a57d3fc",
+               "50e9eee1ac528009e8cbcd356975881f957254b13f91d7c6662d10312052eb00",
+               "2fa0df722a9fd3b64cb18fb2b3db55ff2267422757289413f8f657507412a64c",
+
+               "fe8901fecd3ccd2ec5fdc7c7a0b50519c245b42d611a5ef9e90268d59f3edf33",
+               "bd416cb3b9892228d8f1df575692e4d0",
+               "8d3aa196ec3d7c9b5bb122e7fe77fb1295a6da75abe5d3a510194d3a8a4157d5c89d40619716619859da3ec9b247ced9",
+               "608e82c7ab04007adb22e389a44797fed7de090c8c03ca8a2c5acd9e84df37fbc58ce8edb293e98f02b640d6d1d72464",
+
+               "0493ff637108af6a5b8e90ac1fdf035a3d4bafd1afb573be7ade9e8682e663e5",
+               "c0cd2bebccbb6c49920bd5482ac756e8",
+               "8b37f9148df4bb25956be6310c73c8dc58ea9714ff49b643107b34c9bff096a94fedd6823526abc27a8e0b16616eee254ab4567dd68e8ccd4c38ac563b13639c",
+               "05d5c77729421b08b737e41119fa4438d1f570cc772a4d6c3df7ffeda0384ef84288ce37fc4c4c7d1125a499b051364c389fd639bdda647daa3bdadab2eb5594",
+
+               "9adc8fbd506e032af7fa20cf5343719de6d1288c158c63d6878aaf64ce26ca85",
+               "11958dc6ab81e1c7f01631e9944e620f",
+               "c7917f84f747cd8c4b4fedc2219bdbc5f4d07588389d8248854cf2c2f89667a2d7bcf53e73d32684535f42318e24cd45793950b3825e5d5c5c8fcd3e5dda4ce9246d18337ef3052d8b21c5561c8b660e",
+               "9c99e68236bb2e929db1089c7750f1b356d39ab9d0c40c3e2f05108ae9d0c30b04832ccdbdc08ebfa426b7f5efde986ed05784ce368193bb3699bc691065ac62e258b9aa4cc557e2b45b49ce05511e65",
+
+               "73b8faf00b3302ac99855cf6f9e9e48518690a5906a4869d4dcf48d282faae2a",
+               "b3cb97a80a539912b8c21f450d3b9395",
+               "3adea6e06e42c4f041021491f2775ef6378cb08824165edc4f6448e232175b60d0345b9f9c78df6596ec9d22b7b9e76e8f3c76b32d5d67273f1d83fe7a6fc3dd3c49139170fa5701b3beac61b490f0a9e13f844640c4500f9ad3087adfb0ae10",
+               "ac3d6dbafe2e0f740632fd9e820bf6044cd5b1551cbb9cc03c0b25c39ccb7f33b83aacfca40a3265f2bbff879153448acacb88fcfb3bb7b10fe463a68c0109f028382e3e557b1adf02ed648ab6bb895df0205d26ebbfa9a5fd8cebd8e4bee3dc",
+
+               "9ddf3745896504ff360a51a3eb49c01b79fccebc71c3abcb94a949408b05b2c9",
+               "e79026639d4aa230b5ccffb0b29d79bc",
+               "cf52e5c3954c51b94c9e38acb8c9a7c76aebdaa9943eae0a1ce155a2efdb4d46985d935511471452d9ee64d2461cb2991d59fc0060697f9a671672163230f367fed1422316e52d29eceacb8768f56d9b80f6d278093c9a8acd3cfd7edd8ebd5c293859f64d2f8486ae1bd593c65bc014",
+               "34df561bd2cfebbcb7af3b4b8d21ca5258312e7e2e4e538e35ad2490b6112f0d7f148f6aa8d522a7f3c61d785bd667db0e1dc4606c318ea4f26af4fe7d11d4dcff0456511b4aed1a0d91ba4a1fd6cd9029187bc5881a5a07fe02049d39368e83139b12825bae2c7be81e6f12c61bb5c5",
+
+               "458b67bf212d20f3a57fce392065582dcefbf381aa22949f8338ab9052260e1d",
+               "4c12effc5963d40459602675153e9649",
+               "256fd73ce35ae3ea9c25dd2a9454493e96d8633fe633b56176dce8785ce5dbbb84dbf2c8a2eeb1e96b51899605e4f13bbc11b93bf6f39b3469be14858b5b720d4a522d36feed7a329c9b1e852c9280c47db8039c17c4921571a07d1864128330e09c308ddea1694e95c84500f1a61e614197e86a30ecc28df64ccb3ccf5437aa",
+               "90b7b9630a2378f53f501ab7beff039155008071bc8438e789932cfd3eb1299195465e6633849463fdb44375278e2fdb1310821e6492cf80ff15cb772509fb426f3aeee27bd4938882fd2ae6b5bd9d91fa4a43b17bb439ebbe59c042310163a82a5fe5388796eee35a181a1271f00be29b852d8fa759bad01ff4678f010594cd",
+
+               "d2412db0845d84e5732b8bbd642957473b81fb99ca8bff70e7920d16c1dbec89",
+               "51c619fcf0b23f0c7925f400a6cacb6d",
+               "026006c4a71a180c9929824d9d095b8faaa86fc4fa25ecac61d85ff6de92dfa8702688c02a282c1b8af4449707f22d75e91991015db22374c95f8f195d5bb0afeb03040ff8965e0e1339dba5653e174f8aa5a1b39fe3ac839ce307a4e44b4f8f1b0063f738ec18acdbff2ebfe07383e734558723e741f0a1836dafdf9de82210a9248bc113b3c1bc8b4e252ca01bd803",
+               "0254b23463bcabec5a395eb74c8fb0eb137a07bc6f5e9f61ec0b057de305714f8fa294221c91a159c315939b81e300ee902192ec5f15254428d8772f79324ec43298ca21c00b370273ee5e5ed90e43efa1e05a5d171209fe34f9f29237dba2a6726650fd3b1321747d1208863c6c3c6b3e2d879ab5f25782f08ba8f2abbe63e0bedb4a227e81afb36bb6645508356d34",
+
+               "48be597e632c16772324c8d3fa1d9c5a9ecd010f14ec5d110d3bfec376c5532b",
+               "d6d581b8cf04ebd3b6eaa1b53f047ee1",
+               "0c63d413d3864570e70bb6618bf8a4b9585586688c32bba0a5ecc1362fada74ada32c52acfd1aa7444ba567b4e7daaecf7cc1cb29182af164ae5232b002868695635599807a9a7f07a1f137e97b1e1c9dabc89b6a5e4afa9db5855edaa575056a8f4f8242216242bb0c256310d9d329826ac353d715fa39f80cec144d6424558f9f70b98c920096e0f2c855d594885a00625880e9dfb734163cecef72cf030b8",
+               "fc5873e50de8faf4c6b84ba707b0854e9db9ab2e9f7d707fbba338c6843a18fc6facebaf663d26296fb329b4d26f18494c79e09e779647f9bafa87489630d79f4301610c2300c19dbf3148b7cac8c4f4944102754f332e92b6f7c5e75bc6179eb877a078d4719009021744c14f13fd2a55a2b9c44d18000685a845a4f632c7c56a77306efa66a24d05d088dcd7c13fe24fc447275965db9e4d37fbc9304448cd"
+       };
+
+       /*
+        * AES known-answer tests for CTR.
+        * Order: key, IV, plaintext, ciphertext.
+        */
+       static string[] KAT_AES_CTR = {
+               /*
+                * From RFC 3686.
+                */
+               "ae6852f8121067cc4bf7a5765577f39e",
+               "000000300000000000000000",
+               "53696e676c6520626c6f636b206d7367",
+               "e4095d4fb7a7b3792d6175a3261311b8",
+
+               "7e24067817fae0d743d6ce1f32539163",
+               "006cb6dbc0543b59da48d90b",
+               "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+               "5104a106168a72d9790d41ee8edad388eb2e1efc46da57c8fce630df9141be28",
+
+               "7691be035e5020a8ac6e618529f9a0dc",
+               "00e0017b27777f3f4a1786f0",
+               "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223",
+               "c1cf48a89f2ffdd9cf4652e9efdb72d74540a42bde6d7836d59a5ceaaef3105325b2072f",
+
+               "16af5b145fc9f579c175f93e3bfb0eed863d06ccfdb78515",
+               "0000004836733c147d6d93cb",
+               "53696e676c6520626c6f636b206d7367",
+               "4b55384fe259c9c84e7935a003cbe928",
+
+               "7c5cb2401b3dc33c19e7340819e0f69c678c3db8e6f6a91a",
+               "0096b03b020c6eadc2cb500d",
+               "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+               "453243fc609b23327edfaafa7131cd9f8490701c5ad4a79cfc1fe0ff42f4fb00",
+
+               "02bf391ee8ecb159b959617b0965279bf59b60a786d3e0fe",
+               "0007bdfd5cbd60278dcc0912",
+               "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223",
+               "96893fc55e5c722f540b7dd1ddf7e758d288bc95c69165884536c811662f2188abee0935",
+
+               "776beff2851db06f4c8a0542c8696f6c6a81af1eec96b4d37fc1d689e6c1c104",
+               "00000060db5672c97aa8f0b2",
+               "53696e676c6520626c6f636b206d7367",
+               "145ad01dbf824ec7560863dc71e3e0c0",
+
+               "f6d66d6bd52d59bb0796365879eff886c66dd51a5b6a99744b50590c87a23884",
+               "00faac24c1585ef15a43d875",
+               "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+               "f05e231b3894612c49ee000b804eb2a9b8306b508f839d6a5530831d9344af1c",
+
+               "ff7a617ce69148e4f1726e2f43581de2aa62d9f805532edff1eed687fb54153d",
+               "001cc5b751a51d70a1c11148",
+               "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223",
+               "eb6c52821d0bbbf7ce7594462aca4faab407df866569fd07f48cc0b583d6071f1ec0e6b8"
+       };
+
+       /*
+        * DES known-answer tests.
+        * Order: plaintext, key, ciphertext.
+        * (mostly from NIST SP 800-20).
+        */
+       static string[] KAT_DES_RAW = {
+               "10316E028C8F3B4A", "0000000000000000", "82DCBAFBDEAB6602",
+               "8000000000000000", "0000000000000000", "95A8D72813DAA94D",
+               "4000000000000000", "0000000000000000", "0EEC1487DD8C26D5",
+               "2000000000000000", "0000000000000000", "7AD16FFB79C45926",
+               "1000000000000000", "0000000000000000", "D3746294CA6A6CF3",
+               "0800000000000000", "0000000000000000", "809F5F873C1FD761",
+               "0400000000000000", "0000000000000000", "C02FAFFEC989D1FC",
+               "0200000000000000", "0000000000000000", "4615AA1D33E72F10",
+               "0100000000000000", "0000000000000000", "8CA64DE9C1B123A7",
+               "0080000000000000", "0000000000000000", "2055123350C00858",
+               "0040000000000000", "0000000000000000", "DF3B99D6577397C8",
+               "0020000000000000", "0000000000000000", "31FE17369B5288C9",
+               "0010000000000000", "0000000000000000", "DFDD3CC64DAE1642",
+               "0008000000000000", "0000000000000000", "178C83CE2B399D94",
+               "0004000000000000", "0000000000000000", "50F636324A9B7F80",
+               "0002000000000000", "0000000000000000", "A8468EE3BC18F06D",
+               "0001000000000000", "0000000000000000", "8CA64DE9C1B123A7",
+               "0000800000000000", "0000000000000000", "A2DC9E92FD3CDE92",
+               "0000400000000000", "0000000000000000", "CAC09F797D031287",
+               "0000200000000000", "0000000000000000", "90BA680B22AEB525",
+               "0000100000000000", "0000000000000000", "CE7A24F350E280B6",
+               "0000080000000000", "0000000000000000", "882BFF0AA01A0B87",
+               "0000040000000000", "0000000000000000", "25610288924511C2",
+               "0000020000000000", "0000000000000000", "C71516C29C75D170",
+               "0000010000000000", "0000000000000000", "8CA64DE9C1B123A7",
+               "0000008000000000", "0000000000000000", "5199C29A52C9F059",
+               "0000004000000000", "0000000000000000", "C22F0A294A71F29F",
+               "0000002000000000", "0000000000000000", "EE371483714C02EA",
+               "0000001000000000", "0000000000000000", "A81FBD448F9E522F",
+               "0000000800000000", "0000000000000000", "4F644C92E192DFED",
+               "0000000400000000", "0000000000000000", "1AFA9A66A6DF92AE",
+               "0000000200000000", "0000000000000000", "B3C1CC715CB879D8",
+               "0000000100000000", "0000000000000000", "8CA64DE9C1B123A7",
+               "0000000080000000", "0000000000000000", "19D032E64AB0BD8B",
+               "0000000040000000", "0000000000000000", "3CFAA7A7DC8720DC",
+               "0000000020000000", "0000000000000000", "B7265F7F447AC6F3",
+               "0000000010000000", "0000000000000000", "9DB73B3C0D163F54",
+               "0000000008000000", "0000000000000000", "8181B65BABF4A975",
+               "0000000004000000", "0000000000000000", "93C9B64042EAA240",
+               "0000000002000000", "0000000000000000", "5570530829705592",
+               "0000000001000000", "0000000000000000", "8CA64DE9C1B123A7",
+               "0000000000800000", "0000000000000000", "8638809E878787A0",
+               "0000000000400000", "0000000000000000", "41B9A79AF79AC208",
+               "0000000000200000", "0000000000000000", "7A9BE42F2009A892",
+               "0000000000100000", "0000000000000000", "29038D56BA6D2745",
+               "0000000000080000", "0000000000000000", "5495C6ABF1E5DF51",
+               "0000000000040000", "0000000000000000", "AE13DBD561488933",
+               "0000000000020000", "0000000000000000", "024D1FFA8904E389",
+               "0000000000010000", "0000000000000000", "8CA64DE9C1B123A7",
+               "0000000000008000", "0000000000000000", "D1399712F99BF02E",
+               "0000000000004000", "0000000000000000", "14C1D7C1CFFEC79E",
+               "0000000000002000", "0000000000000000", "1DE5279DAE3BED6F",
+               "0000000000001000", "0000000000000000", "E941A33F85501303",
+               "0000000000000800", "0000000000000000", "DA99DBBC9A03F379",
+               "0000000000000400", "0000000000000000", "B7FC92F91D8E92E9",
+               "0000000000000200", "0000000000000000", "AE8E5CAA3CA04E85",
+               "0000000000000100", "0000000000000000", "8CA64DE9C1B123A7",
+               "0000000000000080", "0000000000000000", "9CC62DF43B6EED74",
+               "0000000000000040", "0000000000000000", "D863DBB5C59A91A0",
+               "0000000000000020", "0000000000000000", "A1AB2190545B91D7",
+               "0000000000000010", "0000000000000000", "0875041E64C570F7",
+               "0000000000000008", "0000000000000000", "5A594528BEBEF1CC",
+               "0000000000000004", "0000000000000000", "FCDB3291DE21F0C0",
+               "0000000000000002", "0000000000000000", "869EFD7F9F265A09",
+               "0000000000000001", "0000000000000000", "8CA64DE9C1B123A7",
+               "0000000000000000", "8000000000000000", "95F8A5E5DD31D900",
+               "0000000000000000", "4000000000000000", "DD7F121CA5015619",
+               "0000000000000000", "2000000000000000", "2E8653104F3834EA",
+               "0000000000000000", "1000000000000000", "4BD388FF6CD81D4F",
+               "0000000000000000", "0800000000000000", "20B9E767B2FB1456",
+               "0000000000000000", "0400000000000000", "55579380D77138EF",
+               "0000000000000000", "0200000000000000", "6CC5DEFAAF04512F",
+               "0000000000000000", "0100000000000000", "0D9F279BA5D87260",
+               "0000000000000000", "0080000000000000", "D9031B0271BD5A0A",
+               "0000000000000000", "0040000000000000", "424250B37C3DD951",
+               "0000000000000000", "0020000000000000", "B8061B7ECD9A21E5",
+               "0000000000000000", "0010000000000000", "F15D0F286B65BD28",
+               "0000000000000000", "0008000000000000", "ADD0CC8D6E5DEBA1",
+               "0000000000000000", "0004000000000000", "E6D5F82752AD63D1",
+               "0000000000000000", "0002000000000000", "ECBFE3BD3F591A5E",
+               "0000000000000000", "0001000000000000", "F356834379D165CD",
+               "0000000000000000", "0000800000000000", "2B9F982F20037FA9",
+               "0000000000000000", "0000400000000000", "889DE068A16F0BE6",
+               "0000000000000000", "0000200000000000", "E19E275D846A1298",
+               "0000000000000000", "0000100000000000", "329A8ED523D71AEC",
+               "0000000000000000", "0000080000000000", "E7FCE22557D23C97",
+               "0000000000000000", "0000040000000000", "12A9F5817FF2D65D",
+               "0000000000000000", "0000020000000000", "A484C3AD38DC9C19",
+               "0000000000000000", "0000010000000000", "FBE00A8A1EF8AD72",
+               "0000000000000000", "0000008000000000", "750D079407521363",
+               "0000000000000000", "0000004000000000", "64FEED9C724C2FAF",
+               "0000000000000000", "0000002000000000", "F02B263B328E2B60",
+               "0000000000000000", "0000001000000000", "9D64555A9A10B852",
+               "0000000000000000", "0000000800000000", "D106FF0BED5255D7",
+               "0000000000000000", "0000000400000000", "E1652C6B138C64A5",
+               "0000000000000000", "0000000200000000", "E428581186EC8F46",
+               "0000000000000000", "0000000100000000", "AEB5F5EDE22D1A36",
+               "0000000000000000", "0000000080000000", "E943D7568AEC0C5C",
+               "0000000000000000", "0000000040000000", "DF98C8276F54B04B",
+               "0000000000000000", "0000000020000000", "B160E4680F6C696F",
+               "0000000000000000", "0000000010000000", "FA0752B07D9C4AB8",
+               "0000000000000000", "0000000008000000", "CA3A2B036DBC8502",
+               "0000000000000000", "0000000004000000", "5E0905517BB59BCF",
+               "0000000000000000", "0000000002000000", "814EEB3B91D90726",
+               "0000000000000000", "0000000001000000", "4D49DB1532919C9F",
+               "0000000000000000", "0000000000800000", "25EB5FC3F8CF0621",
+               "0000000000000000", "0000000000400000", "AB6A20C0620D1C6F",
+               "0000000000000000", "0000000000200000", "79E90DBC98F92CCA",
+               "0000000000000000", "0000000000100000", "866ECEDD8072BB0E",
+               "0000000000000000", "0000000000080000", "8B54536F2F3E64A8",
+               "0000000000000000", "0000000000040000", "EA51D3975595B86B",
+               "0000000000000000", "0000000000020000", "CAFFC6AC4542DE31",
+               "0000000000000000", "0000000000010000", "8DD45A2DDF90796C",
+               "0000000000000000", "0000000000008000", "1029D55E880EC2D0",
+               "0000000000000000", "0000000000004000", "5D86CB23639DBEA9",
+               "0000000000000000", "0000000000002000", "1D1CA853AE7C0C5F",
+               "0000000000000000", "0000000000001000", "CE332329248F3228",
+               "0000000000000000", "0000000000000800", "8405D1ABE24FB942",
+               "0000000000000000", "0000000000000400", "E643D78090CA4207",
+               "0000000000000000", "0000000000000200", "48221B9937748A23",
+               "0000000000000000", "0000000000000100", "DD7C0BBD61FAFD54",
+               "0000000000000000", "0000000000000080", "2FBC291A570DB5C4",
+               "0000000000000000", "0000000000000040", "E07C30D7E4E26E12",
+               "0000000000000000", "0000000000000020", "0953E2258E8E90A1",
+               "0000000000000000", "0000000000000010", "5B711BC4CEEBF2EE",
+               "0000000000000000", "0000000000000008", "CC083F1E6D9E85F6",
+               "0000000000000000", "0000000000000004", "D2FD8867D50D2DFE",
+               "0000000000000000", "0000000000000002", "06E7EA22CE92708F",
+               "0000000000000000", "0000000000000001", "166B40B44ABA4BD6",
+               "0000000000000000", "0000000000000000", "8CA64DE9C1B123A7",
+               "0101010101010101", "0101010101010101", "994D4DC157B96C52",
+               "0202020202020202", "0202020202020202", "E127C2B61D98E6E2",
+               "0303030303030303", "0303030303030303", "984C91D78A269CE3",
+               "0404040404040404", "0404040404040404", "1F4570BB77550683",
+               "0505050505050505", "0505050505050505", "3990ABF98D672B16",
+               "0606060606060606", "0606060606060606", "3F5150BBA081D585",
+               "0707070707070707", "0707070707070707", "C65242248C9CF6F2",
+               "0808080808080808", "0808080808080808", "10772D40FAD24257",
+               "0909090909090909", "0909090909090909", "F0139440647A6E7B",
+               "0A0A0A0A0A0A0A0A", "0A0A0A0A0A0A0A0A", "0A288603044D740C",
+               "0B0B0B0B0B0B0B0B", "0B0B0B0B0B0B0B0B", "6359916942F7438F",
+               "0C0C0C0C0C0C0C0C", "0C0C0C0C0C0C0C0C", "934316AE443CF08B",
+               "0D0D0D0D0D0D0D0D", "0D0D0D0D0D0D0D0D", "E3F56D7F1130A2B7",
+               "0E0E0E0E0E0E0E0E", "0E0E0E0E0E0E0E0E", "A2E4705087C6B6B4",
+               "0F0F0F0F0F0F0F0F", "0F0F0F0F0F0F0F0F", "D5D76E09A447E8C3",
+               "1010101010101010", "1010101010101010", "DD7515F2BFC17F85",
+               "1111111111111111", "1111111111111111", "F40379AB9E0EC533",
+               "1212121212121212", "1212121212121212", "96CD27784D1563E5",
+               "1313131313131313", "1313131313131313", "2911CF5E94D33FE1",
+               "1414141414141414", "1414141414141414", "377B7F7CA3E5BBB3",
+               "1515151515151515", "1515151515151515", "701AA63832905A92",
+               "1616161616161616", "1616161616161616", "2006E716C4252D6D",
+               "1717171717171717", "1717171717171717", "452C1197422469F8",
+               "1818181818181818", "1818181818181818", "C33FD1EB49CB64DA",
+               "1919191919191919", "1919191919191919", "7572278F364EB50D",
+               "1A1A1A1A1A1A1A1A", "1A1A1A1A1A1A1A1A", "69E51488403EF4C3",
+               "1B1B1B1B1B1B1B1B", "1B1B1B1B1B1B1B1B", "FF847E0ADF192825",
+               "1C1C1C1C1C1C1C1C", "1C1C1C1C1C1C1C1C", "521B7FB3B41BB791",
+               "1D1D1D1D1D1D1D1D", "1D1D1D1D1D1D1D1D", "26059A6A0F3F6B35",
+               "1E1E1E1E1E1E1E1E", "1E1E1E1E1E1E1E1E", "F24A8D2231C77538",
+               "1F1F1F1F1F1F1F1F", "1F1F1F1F1F1F1F1F", "4FD96EC0D3304EF6",
+               "2020202020202020", "2020202020202020", "18A9D580A900B699",
+               "2121212121212121", "2121212121212121", "88586E1D755B9B5A",
+               "2222222222222222", "2222222222222222", "0F8ADFFB11DC2784",
+               "2323232323232323", "2323232323232323", "2F30446C8312404A",
+               "2424242424242424", "2424242424242424", "0BA03D9E6C196511",
+               "2525252525252525", "2525252525252525", "3E55E997611E4B7D",
+               "2626262626262626", "2626262626262626", "B2522FB5F158F0DF",
+               "2727272727272727", "2727272727272727", "2109425935406AB8",
+               "2828282828282828", "2828282828282828", "11A16028F310FF16",
+               "2929292929292929", "2929292929292929", "73F0C45F379FE67F",
+               "2A2A2A2A2A2A2A2A", "2A2A2A2A2A2A2A2A", "DCAD4338F7523816",
+               "2B2B2B2B2B2B2B2B", "2B2B2B2B2B2B2B2B", "B81634C1CEAB298C",
+               "2C2C2C2C2C2C2C2C", "2C2C2C2C2C2C2C2C", "DD2CCB29B6C4C349",
+               "2D2D2D2D2D2D2D2D", "2D2D2D2D2D2D2D2D", "7D07A77A2ABD50A7",
+               "2E2E2E2E2E2E2E2E", "2E2E2E2E2E2E2E2E", "30C1B0C1FD91D371",
+               "2F2F2F2F2F2F2F2F", "2F2F2F2F2F2F2F2F", "C4427B31AC61973B",
+               "3030303030303030", "3030303030303030", "F47BB46273B15EB5",
+               "3131313131313131", "3131313131313131", "655EA628CF62585F",
+               "3232323232323232", "3232323232323232", "AC978C247863388F",
+               "3333333333333333", "3333333333333333", "0432ED386F2DE328",
+               "3434343434343434", "3434343434343434", "D254014CB986B3C2",
+               "3535353535353535", "3535353535353535", "B256E34BEDB49801",
+               "3636363636363636", "3636363636363636", "37F8759EB77E7BFC",
+               "3737373737373737", "3737373737373737", "5013CA4F62C9CEA0",
+               "3838383838383838", "3838383838383838", "8940F7B3EACA5939",
+               "3939393939393939", "3939393939393939", "E22B19A55086774B",
+               "3A3A3A3A3A3A3A3A", "3A3A3A3A3A3A3A3A", "B04A2AAC925ABB0B",
+               "3B3B3B3B3B3B3B3B", "3B3B3B3B3B3B3B3B", "8D250D58361597FC",
+               "3C3C3C3C3C3C3C3C", "3C3C3C3C3C3C3C3C", "51F0114FB6A6CD37",
+               "3D3D3D3D3D3D3D3D", "3D3D3D3D3D3D3D3D", "9D0BB4DB830ECB73",
+               "3E3E3E3E3E3E3E3E", "3E3E3E3E3E3E3E3E", "E96089D6368F3E1A",
+               "3F3F3F3F3F3F3F3F", "3F3F3F3F3F3F3F3F", "5C4CA877A4E1E92D",
+               "4040404040404040", "4040404040404040", "6D55DDBC8DEA95FF",
+               "4141414141414141", "4141414141414141", "19DF84AC95551003",
+               "4242424242424242", "4242424242424242", "724E7332696D08A7",
+               "4343434343434343", "4343434343434343", "B91810B8CDC58FE2",
+               "4444444444444444", "4444444444444444", "06E23526EDCCD0C4",
+               "4545454545454545", "4545454545454545", "EF52491D5468D441",
+               "4646464646464646", "4646464646464646", "48019C59E39B90C5",
+               "4747474747474747", "4747474747474747", "0544083FB902D8C0",
+               "4848484848484848", "4848484848484848", "63B15CADA668CE12",
+               "4949494949494949", "4949494949494949", "EACC0C1264171071",
+               "4A4A4A4A4A4A4A4A", "4A4A4A4A4A4A4A4A", "9D2B8C0AC605F274",
+               "4B4B4B4B4B4B4B4B", "4B4B4B4B4B4B4B4B", "C90F2F4C98A8FB2A",
+               "4C4C4C4C4C4C4C4C", "4C4C4C4C4C4C4C4C", "03481B4828FD1D04",
+               "4D4D4D4D4D4D4D4D", "4D4D4D4D4D4D4D4D", "C78FC45A1DCEA2E2",
+               "4E4E4E4E4E4E4E4E", "4E4E4E4E4E4E4E4E", "DB96D88C3460D801",
+               "4F4F4F4F4F4F4F4F", "4F4F4F4F4F4F4F4F", "6C69E720F5105518",
+               "5050505050505050", "5050505050505050", "0D262E418BC893F3",
+               "5151515151515151", "5151515151515151", "6AD84FD7848A0A5C",
+               "5252525252525252", "5252525252525252", "C365CB35B34B6114",
+               "5353535353535353", "5353535353535353", "1155392E877F42A9",
+               "5454545454545454", "5454545454545454", "531BE5F9405DA715",
+               "5555555555555555", "5555555555555555", "3BCDD41E6165A5E8",
+               "5656565656565656", "5656565656565656", "2B1FF5610A19270C",
+               "5757575757575757", "5757575757575757", "D90772CF3F047CFD",
+               "5858585858585858", "5858585858585858", "1BEA27FFB72457B7",
+               "5959595959595959", "5959595959595959", "85C3E0C429F34C27",
+               "5A5A5A5A5A5A5A5A", "5A5A5A5A5A5A5A5A", "F9038021E37C7618",
+               "5B5B5B5B5B5B5B5B", "5B5B5B5B5B5B5B5B", "35BC6FF838DBA32F",
+               "5C5C5C5C5C5C5C5C", "5C5C5C5C5C5C5C5C", "4927ACC8CE45ECE7",
+               "5D5D5D5D5D5D5D5D", "5D5D5D5D5D5D5D5D", "E812EE6E3572985C",
+               "5E5E5E5E5E5E5E5E", "5E5E5E5E5E5E5E5E", "9BB93A89627BF65F",
+               "5F5F5F5F5F5F5F5F", "5F5F5F5F5F5F5F5F", "EF12476884CB74CA",
+               "6060606060606060", "6060606060606060", "1BF17E00C09E7CBF",
+               "6161616161616161", "6161616161616161", "29932350C098DB5D",
+               "6262626262626262", "6262626262626262", "B476E6499842AC54",
+               "6363636363636363", "6363636363636363", "5C662C29C1E96056",
+               "6464646464646464", "6464646464646464", "3AF1703D76442789",
+               "6565656565656565", "6565656565656565", "86405D9B425A8C8C",
+               "6666666666666666", "6666666666666666", "EBBF4810619C2C55",
+               "6767676767676767", "6767676767676767", "F8D1CD7367B21B5D",
+               "6868686868686868", "6868686868686868", "9EE703142BF8D7E2",
+               "6969696969696969", "6969696969696969", "5FDFFFC3AAAB0CB3",
+               "6A6A6A6A6A6A6A6A", "6A6A6A6A6A6A6A6A", "26C940AB13574231",
+               "6B6B6B6B6B6B6B6B", "6B6B6B6B6B6B6B6B", "1E2DC77E36A84693",
+               "6C6C6C6C6C6C6C6C", "6C6C6C6C6C6C6C6C", "0F4FF4D9BC7E2244",
+               "6D6D6D6D6D6D6D6D", "6D6D6D6D6D6D6D6D", "A4C9A0D04D3280CD",
+               "6E6E6E6E6E6E6E6E", "6E6E6E6E6E6E6E6E", "9FAF2C96FE84919D",
+               "6F6F6F6F6F6F6F6F", "6F6F6F6F6F6F6F6F", "115DBC965E6096C8",
+               "7070707070707070", "7070707070707070", "AF531E9520994017",
+               "7171717171717171", "7171717171717171", "B971ADE70E5C89EE",
+               "7272727272727272", "7272727272727272", "415D81C86AF9C376",
+               "7373737373737373", "7373737373737373", "8DFB864FDB3C6811",
+               "7474747474747474", "7474747474747474", "10B1C170E3398F91",
+               "7575757575757575", "7575757575757575", "CFEF7A1C0218DB1E",
+               "7676767676767676", "7676767676767676", "DBAC30A2A40B1B9C",
+               "7777777777777777", "7777777777777777", "89D3BF37052162E9",
+               "7878787878787878", "7878787878787878", "80D9230BDAEB67DC",
+               "7979797979797979", "7979797979797979", "3440911019AD68D7",
+               "7A7A7A7A7A7A7A7A", "7A7A7A7A7A7A7A7A", "9626FE57596E199E",
+               "7B7B7B7B7B7B7B7B", "7B7B7B7B7B7B7B7B", "DEA0B796624BB5BA",
+               "7C7C7C7C7C7C7C7C", "7C7C7C7C7C7C7C7C", "E9E40542BDDB3E9D",
+               "7D7D7D7D7D7D7D7D", "7D7D7D7D7D7D7D7D", "8AD99914B354B911",
+               "7E7E7E7E7E7E7E7E", "7E7E7E7E7E7E7E7E", "6F85B98DD12CB13B",
+               "7F7F7F7F7F7F7F7F", "7F7F7F7F7F7F7F7F", "10130DA3C3A23924",
+               "8080808080808080", "8080808080808080", "EFECF25C3C5DC6DB",
+               "8181818181818181", "8181818181818181", "907A46722ED34EC4",
+               "8282828282828282", "8282828282828282", "752666EB4CAB46EE",
+               "8383838383838383", "8383838383838383", "161BFABD4224C162",
+               "8484848484848484", "8484848484848484", "215F48699DB44A45",
+               "8585858585858585", "8585858585858585", "69D901A8A691E661",
+               "8686868686868686", "8686868686868686", "CBBF6EEFE6529728",
+               "8787878787878787", "8787878787878787", "7F26DCF425149823",
+               "8888888888888888", "8888888888888888", "762C40C8FADE9D16",
+               "8989898989898989", "8989898989898989", "2453CF5D5BF4E463",
+               "8A8A8A8A8A8A8A8A", "8A8A8A8A8A8A8A8A", "301085E3FDE724E1",
+               "8B8B8B8B8B8B8B8B", "8B8B8B8B8B8B8B8B", "EF4E3E8F1CC6706E",
+               "8C8C8C8C8C8C8C8C", "8C8C8C8C8C8C8C8C", "720479B024C397EE",
+               "8D8D8D8D8D8D8D8D", "8D8D8D8D8D8D8D8D", "BEA27E3795063C89",
+               "8E8E8E8E8E8E8E8E", "8E8E8E8E8E8E8E8E", "468E5218F1A37611",
+               "8F8F8F8F8F8F8F8F", "8F8F8F8F8F8F8F8F", "50ACE16ADF66BFE8",
+               "9090909090909090", "9090909090909090", "EEA24369A19F6937",
+               "9191919191919191", "9191919191919191", "6050D369017B6E62",
+               "9292929292929292", "9292929292929292", "5B365F2FB2CD7F32",
+               "9393939393939393", "9393939393939393", "F0B00B264381DDBB",
+               "9494949494949494", "9494949494949494", "E1D23881C957B96C",
+               "9595959595959595", "9595959595959595", "D936BF54ECA8BDCE",
+               "9696969696969696", "9696969696969696", "A020003C5554F34C",
+               "9797979797979797", "9797979797979797", "6118FCEBD407281D",
+               "9898989898989898", "9898989898989898", "072E328C984DE4A2",
+               "9999999999999999", "9999999999999999", "1440B7EF9E63D3AA",
+               "9A9A9A9A9A9A9A9A", "9A9A9A9A9A9A9A9A", "79BFA264BDA57373",
+               "9B9B9B9B9B9B9B9B", "9B9B9B9B9B9B9B9B", "C50E8FC289BBD876",
+               "9C9C9C9C9C9C9C9C", "9C9C9C9C9C9C9C9C", "A399D3D63E169FA9",
+               "9D9D9D9D9D9D9D9D", "9D9D9D9D9D9D9D9D", "4B8919B667BD53AB",
+               "9E9E9E9E9E9E9E9E", "9E9E9E9E9E9E9E9E", "D66CDCAF3F6724A2",
+               "9F9F9F9F9F9F9F9F", "9F9F9F9F9F9F9F9F", "E40E81FF3F618340",
+               "A0A0A0A0A0A0A0A0", "A0A0A0A0A0A0A0A0", "10EDB8977B348B35",
+               "A1A1A1A1A1A1A1A1", "A1A1A1A1A1A1A1A1", "6446C5769D8409A0",
+               "A2A2A2A2A2A2A2A2", "A2A2A2A2A2A2A2A2", "17ED1191CA8D67A3",
+               "A3A3A3A3A3A3A3A3", "A3A3A3A3A3A3A3A3", "B6D8533731BA1318",
+               "A4A4A4A4A4A4A4A4", "A4A4A4A4A4A4A4A4", "CA439007C7245CD0",
+               "A5A5A5A5A5A5A5A5", "A5A5A5A5A5A5A5A5", "06FC7FDE1C8389E7",
+               "A6A6A6A6A6A6A6A6", "A6A6A6A6A6A6A6A6", "7A3C1F3BD60CB3D8",
+               "A7A7A7A7A7A7A7A7", "A7A7A7A7A7A7A7A7", "E415D80048DBA848",
+               "A8A8A8A8A8A8A8A8", "A8A8A8A8A8A8A8A8", "26F88D30C0FB8302",
+               "A9A9A9A9A9A9A9A9", "A9A9A9A9A9A9A9A9", "D4E00A9EF5E6D8F3",
+               "AAAAAAAAAAAAAAAA", "AAAAAAAAAAAAAAAA", "C4322BE19E9A5A17",
+               "ABABABABABABABAB", "ABABABABABABABAB", "ACE41A06BFA258EA",
+               "ACACACACACACACAC", "ACACACACACACACAC", "EEAAC6D17880BD56",
+               "ADADADADADADADAD", "ADADADADADADADAD", "3C9A34CA4CB49EEB",
+               "AEAEAEAEAEAEAEAE", "AEAEAEAEAEAEAEAE", "9527B0287B75F5A3",
+               "AFAFAFAFAFAFAFAF", "AFAFAFAFAFAFAFAF", "F2D9D1BE74376C0C",
+               "B0B0B0B0B0B0B0B0", "B0B0B0B0B0B0B0B0", "939618DF0AEFAAE7",
+               "B1B1B1B1B1B1B1B1", "B1B1B1B1B1B1B1B1", "24692773CB9F27FE",
+               "B2B2B2B2B2B2B2B2", "B2B2B2B2B2B2B2B2", "38703BA5E2315D1D",
+               "B3B3B3B3B3B3B3B3", "B3B3B3B3B3B3B3B3", "FCB7E4B7D702E2FB",
+               "B4B4B4B4B4B4B4B4", "B4B4B4B4B4B4B4B4", "36F0D0B3675704D5",
+               "B5B5B5B5B5B5B5B5", "B5B5B5B5B5B5B5B5", "62D473F539FA0D8B",
+               "B6B6B6B6B6B6B6B6", "B6B6B6B6B6B6B6B6", "1533F3ED9BE8EF8E",
+               "B7B7B7B7B7B7B7B7", "B7B7B7B7B7B7B7B7", "9C4EA352599731ED",
+               "B8B8B8B8B8B8B8B8", "B8B8B8B8B8B8B8B8", "FABBF7C046FD273F",
+               "B9B9B9B9B9B9B9B9", "B9B9B9B9B9B9B9B9", "B7FE63A61C646F3A",
+               "BABABABABABABABA", "BABABABABABABABA", "10ADB6E2AB972BBE",
+               "BBBBBBBBBBBBBBBB", "BBBBBBBBBBBBBBBB", "F91DCAD912332F3B",
+               "BCBCBCBCBCBCBCBC", "BCBCBCBCBCBCBCBC", "46E7EF47323A701D",
+               "BDBDBDBDBDBDBDBD", "BDBDBDBDBDBDBDBD", "8DB18CCD9692F758",
+               "BEBEBEBEBEBEBEBE", "BEBEBEBEBEBEBEBE", "E6207B536AAAEFFC",
+               "BFBFBFBFBFBFBFBF", "BFBFBFBFBFBFBFBF", "92AA224372156A00",
+               "C0C0C0C0C0C0C0C0", "C0C0C0C0C0C0C0C0", "A3B357885B1E16D2",
+               "C1C1C1C1C1C1C1C1", "C1C1C1C1C1C1C1C1", "169F7629C970C1E5",
+               "C2C2C2C2C2C2C2C2", "C2C2C2C2C2C2C2C2", "62F44B247CF1348C",
+               "C3C3C3C3C3C3C3C3", "C3C3C3C3C3C3C3C3", "AE0FEEB0495932C8",
+               "C4C4C4C4C4C4C4C4", "C4C4C4C4C4C4C4C4", "72DAF2A7C9EA6803",
+               "C5C5C5C5C5C5C5C5", "C5C5C5C5C5C5C5C5", "4FB5D5536DA544F4",
+               "C6C6C6C6C6C6C6C6", "C6C6C6C6C6C6C6C6", "1DD4E65AAF7988B4",
+               "C7C7C7C7C7C7C7C7", "C7C7C7C7C7C7C7C7", "76BF084C1535A6C6",
+               "C8C8C8C8C8C8C8C8", "C8C8C8C8C8C8C8C8", "AFEC35B09D36315F",
+               "C9C9C9C9C9C9C9C9", "C9C9C9C9C9C9C9C9", "C8078A6148818403",
+               "CACACACACACACACA", "CACACACACACACACA", "4DA91CB4124B67FE",
+               "CBCBCBCBCBCBCBCB", "CBCBCBCBCBCBCBCB", "2DABFEB346794C3D",
+               "CCCCCCCCCCCCCCCC", "CCCCCCCCCCCCCCCC", "FBCD12C790D21CD7",
+               "CDCDCDCDCDCDCDCD", "CDCDCDCDCDCDCDCD", "536873DB879CC770",
+               "CECECECECECECECE", "CECECECECECECECE", "9AA159D7309DA7A0",
+               "CFCFCFCFCFCFCFCF", "CFCFCFCFCFCFCFCF", "0B844B9D8C4EA14A",
+               "D0D0D0D0D0D0D0D0", "D0D0D0D0D0D0D0D0", "3BBD84CE539E68C4",
+               "D1D1D1D1D1D1D1D1", "D1D1D1D1D1D1D1D1", "CF3E4F3E026E2C8E",
+               "D2D2D2D2D2D2D2D2", "D2D2D2D2D2D2D2D2", "82F85885D542AF58",
+               "D3D3D3D3D3D3D3D3", "D3D3D3D3D3D3D3D3", "22D334D6493B3CB6",
+               "D4D4D4D4D4D4D4D4", "D4D4D4D4D4D4D4D4", "47E9CB3E3154D673",
+               "D5D5D5D5D5D5D5D5", "D5D5D5D5D5D5D5D5", "2352BCC708ADC7E9",
+               "D6D6D6D6D6D6D6D6", "D6D6D6D6D6D6D6D6", "8C0F3BA0C8601980",
+               "D7D7D7D7D7D7D7D7", "D7D7D7D7D7D7D7D7", "EE5E9FD70CEF00E9",
+               "D8D8D8D8D8D8D8D8", "D8D8D8D8D8D8D8D8", "DEF6BDA6CABF9547",
+               "D9D9D9D9D9D9D9D9", "D9D9D9D9D9D9D9D9", "4DADD04A0EA70F20",
+               "DADADADADADADADA", "DADADADADADADADA", "C1AA16689EE1B482",
+               "DBDBDBDBDBDBDBDB", "DBDBDBDBDBDBDBDB", "F45FC26193E69AEE",
+               "DCDCDCDCDCDCDCDC", "DCDCDCDCDCDCDCDC", "D0CFBB937CEDBFB5",
+               "DDDDDDDDDDDDDDDD", "DDDDDDDDDDDDDDDD", "F0752004EE23D87B",
+               "DEDEDEDEDEDEDEDE", "DEDEDEDEDEDEDEDE", "77A791E28AA464A5",
+               "DFDFDFDFDFDFDFDF", "DFDFDFDFDFDFDFDF", "E7562A7F56FF4966",
+               "E0E0E0E0E0E0E0E0", "E0E0E0E0E0E0E0E0", "B026913F2CCFB109",
+               "E1E1E1E1E1E1E1E1", "E1E1E1E1E1E1E1E1", "0DB572DDCE388AC7",
+               "E2E2E2E2E2E2E2E2", "E2E2E2E2E2E2E2E2", "D9FA6595F0C094CA",
+               "E3E3E3E3E3E3E3E3", "E3E3E3E3E3E3E3E3", "ADE4804C4BE4486E",
+               "E4E4E4E4E4E4E4E4", "E4E4E4E4E4E4E4E4", "007B81F520E6D7DA",
+               "E5E5E5E5E5E5E5E5", "E5E5E5E5E5E5E5E5", "961AEB77BFC10B3C",
+               "E6E6E6E6E6E6E6E6", "E6E6E6E6E6E6E6E6", "8A8DD870C9B14AF2",
+               "E7E7E7E7E7E7E7E7", "E7E7E7E7E7E7E7E7", "3CC02E14B6349B25",
+               "E8E8E8E8E8E8E8E8", "E8E8E8E8E8E8E8E8", "BAD3EE68BDDB9607",
+               "E9E9E9E9E9E9E9E9", "E9E9E9E9E9E9E9E9", "DFF918E93BDAD292",
+               "EAEAEAEAEAEAEAEA", "EAEAEAEAEAEAEAEA", "8FE559C7CD6FA56D",
+               "EBEBEBEBEBEBEBEB", "EBEBEBEBEBEBEBEB", "C88480835C1A444C",
+               "ECECECECECECECEC", "ECECECECECECECEC", "D6EE30A16B2CC01E",
+               "EDEDEDEDEDEDEDED", "EDEDEDEDEDEDEDED", "6932D887B2EA9C1A",
+               "EEEEEEEEEEEEEEEE", "EEEEEEEEEEEEEEEE", "0BFC865461F13ACC",
+               "EFEFEFEFEFEFEFEF", "EFEFEFEFEFEFEFEF", "228AEA0D403E807A",
+               "F0F0F0F0F0F0F0F0", "F0F0F0F0F0F0F0F0", "2A2891F65BB8173C",
+               "F1F1F1F1F1F1F1F1", "F1F1F1F1F1F1F1F1", "5D1B8FAF7839494B",
+               "F2F2F2F2F2F2F2F2", "F2F2F2F2F2F2F2F2", "1C0A9280EECF5D48",
+               "F3F3F3F3F3F3F3F3", "F3F3F3F3F3F3F3F3", "6CBCE951BBC30F74",
+               "F4F4F4F4F4F4F4F4", "F4F4F4F4F4F4F4F4", "9CA66E96BD08BC70",
+               "F5F5F5F5F5F5F5F5", "F5F5F5F5F5F5F5F5", "F5D779FCFBB28BF3",
+               "F6F6F6F6F6F6F6F6", "F6F6F6F6F6F6F6F6", "0FEC6BBF9B859184",
+               "F7F7F7F7F7F7F7F7", "F7F7F7F7F7F7F7F7", "EF88D2BF052DBDA8",
+               "F8F8F8F8F8F8F8F8", "F8F8F8F8F8F8F8F8", "39ADBDDB7363090D",
+               "F9F9F9F9F9F9F9F9", "F9F9F9F9F9F9F9F9", "C0AEAF445F7E2A7A",
+               "FAFAFAFAFAFAFAFA", "FAFAFAFAFAFAFAFA", "C66F54067298D4E9",
+               "FBFBFBFBFBFBFBFB", "FBFBFBFBFBFBFBFB", "E0BA8F4488AAF97C",
+               "FCFCFCFCFCFCFCFC", "FCFCFCFCFCFCFCFC", "67B36E2875D9631C",
+               "FDFDFDFDFDFDFDFD", "FDFDFDFDFDFDFDFD", "1ED83D49E267191D",
+               "FEFEFEFEFEFEFEFE", "FEFEFEFEFEFEFEFE", "66B2B23EA84693AD",
+               "FFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", "7359B2163E4EDC58",
+               "0001020304050607", "0011223344556677", "3EF0A891CF8ED990",
+               "2BD6459F82C5B300", "EA024714AD5C4D84", "126EFE8ED312190A"
+       };
+
+       /*
+        * Known-answer tests for DES/3DES in CBC mode.
+        * Order: key, IV, plaintext, ciphertext.
+        */
+       static string[] KAT_DES_CBC = {
+               /*
+                * From NIST validation suite (tdesmmt.zip).
+                */
+               "34a41a8c293176c1b30732ecfe38ae8a34a41a8c293176c1",
+               "f55b4855228bd0b4",
+               "7dd880d2a9ab411c",
+               "c91892948b6cadb4",
+
+               "70a88fa1dfb9942fa77f40157ffef2ad70a88fa1dfb9942f",
+               "ece08ce2fdc6ce80",
+               "bc225304d5a3a5c9918fc5006cbc40cc",
+               "27f67dc87af7ddb4b68f63fa7c2d454a",
+
+               "e091790be55be0bc0780153861a84adce091790be55be0bc",
+               "fd7d430f86fbbffe",
+               "03c7fffd7f36499c703dedc9df4de4a92dd4382e576d6ae9",
+               "053aeba85dd3a23bfbe8440a432f9578f312be60fb9f0035",
+
+               "857feacd16157c58e5347a70e56e578a857feacd16157c58",
+               "002dcb6d46ef0969",
+               "1f13701c7f0d7385307507a18e89843ebd295bd5e239ef109347a6898c6d3fd5",
+               "a0e4edde34f05bd8397ce279e49853e9387ba04be562f5fa19c3289c3f5a3391",
+
+               "a173545b265875ba852331fbb95b49a8a173545b265875ba",
+               "ab385756391d364c",
+               "d08894c565608d9ae51dda63b85b3b33b1703bb5e4f1abcbb8794e743da5d6f3bf630f2e9b6d5b54",
+               "370b47acf89ac6bdbb13c9a7336787dc41e1ad8beead32281d0609fb54968404bdf2894892590658",
+
+               "26376bcb2f23df1083cd684fe00ed3c726376bcb2f23df10",
+               "33acfb0f3d240ea6",
+               "903a1911da1e6877f23c1985a9b61786ef438e0ce1240885035ad60fc916b18e5d71a1fb9c5d1eff61db75c0076f6efb",
+               "7a4f7510f6ec0b93e2495d21a8355684d303a770ebda2e0e51ff33d72b20cb73e58e2e3de2ef6b2e12c504c0f181ba63",
+
+               "3e1f98135d027cec752f67765408a7913e1f98135d027cec",
+               "11f5f2304b28f68b",
+               "7c022f5af24f7925d323d4d0e20a2ce49272c5e764b22c806f4b6ddc406d864fe5bd1c3f45556d3eb30c8676c2f8b54a5a32423a0bd95a07",
+               "2bb4b131fa4ae0b4f0378a2cdb68556af6eee837613016d7ea936f3931f25f8b3ae351d5e9d00be665676e2400408b5db9892d95421e7f1a",
+
+               "13b9d549cd136ec7bf9e9810ef2cdcbf13b9d549cd136ec7",
+               "a82c1b1057badcc8",
+               "1fff1563bc1645b55cb23ea34a0049dfc06607150614b621dedcb07f20433402a2d869c95ac4a070c7a3da838c928a385f899c5d21ecb58f4e5cbdad98d39b8c",
+               "75f804d4a2c542a31703e23df26cc38861a0729090e6eae5672c1db8c0b09fba9b125bbca7d6c7d330b3859e6725c6d26de21c4e3af7f5ea94df3cde2349ce37",
+
+               "20320dfdad579bb57c6e4acd769dbadf20320dfdad579bb5",
+               "879201b5857ccdea",
+               "0431283cc8bb4dc7750a9d5c68578486932091632a12d0a79f2c54e3d122130881fff727050f317a40fcd1a8d13793458b99fc98254ba6a233e3d95b55cf5a3faff78809999ea4bf",
+               "85d17840eb2af5fc727027336bfd71a2b31bd14a1d9eb64f8a08bfc4f56eaa9ca7654a5ae698287869cc27324813730de4f1384e0b8cfbc472ff5470e3c5e4bd8ceb23dc2d91988c",
+
+               "23abb073a2df34cb3d1fdce6b092582c23abb073a2df34cb",
+               "7d7fbf19e8562d32",
+               "31e718fd95e6d7ca4f94763191add2674ab07c909d88c486916c16d60a048a0cf8cdb631cebec791362cd0c202eb61e166b65c1f65d0047c8aec57d3d84b9e17032442dce148e1191b06a12c284cc41e",
+               "c9a3f75ab6a7cd08a7fd53ca540aafe731d257ee1c379fadcc4cc1a06e7c12bddbeb7562c436d1da849ed072629e82a97b56d9becc25ff4f16f21c5f2a01911604f0b5c49df96cb641faee662ca8aa68",
+
+               "b5cb1504802326c73df186e3e352a20de643b0d63ee30e37",
+               "43f791134c5647ba",
+               "dcc153cef81d6f24",
+               "92538bd8af18d3ba",
+
+               "a49d7564199e97cb529d2c9d97bf2f98d35edf57ba1f7358",
+               "c2e999cb6249023c",
+               "c689aee38a301bb316da75db36f110b5",
+               "e9afaba5ec75ea1bbe65506655bb4ecb",
+
+               "1a5d4c0825072a15a8ad9dfdaeda8c048adffb85bc4fced0",
+               "7fcfa736f7548b6f",
+               "983c3edacd939406010e1bc6ff9e12320ac5008117fa8f84",
+               "d84fa24f38cf451ca2c9adc960120bd8ff9871584fe31cee",
+
+               "d98aadc76d4a3716158c32866efbb9ce834af2297379a49d",
+               "3c5220327c502b44",
+               "6174079dda53ca723ebf00a66837f8d5ce648c08acaa5ee45ffe62210ef79d3e",
+               "f5bd4d600bed77bec78409e3530ebda1d815506ed53103015b87e371ae000958",
+
+               "ef6d3e54266d978ffb0b8ce6689d803e2cd34cc802fd0252",
+               "38bae5bce06d0ad9",
+               "c4f228b537223cd01c0debb5d9d4e12ba71656618d119b2f8f0af29d23efa3a9e43c4c458a1b79a0",
+               "9e3289fb18379f55aa4e45a7e0e6df160b33b75f8627ad0954f8fdcb78cee55a4664caeda1000fe5",
+
+               "625bc19b19df83abfb2f5bec9d4f2062017525a75bc26e70",
+               "bd0cff364ff69a91",
+               "8152d2ab876c3c8201403a5a406d3feaf27319dbea6ad01e24f4d18203704b86de70da6bbb6d638e5aba3ff576b79b28",
+               "706fe7a973fac40e25b2b4499ce527078944c70e976d017b6af86a3a7a6b52943a72ba18a58000d2b61fdc3bfef2bc4a",
+
+               "b6383176046e6880a1023bf45768b5bf5119022fe054bfe5",
+               "ec13ca541c43401e",
+               "cd5a886e9af011346c4dba36a424f96a78a1ddf28aaa4188bf65451f4efaffc7179a6dd237c0ae35d9b672314e5cb032612597f7e462c6f3",
+               "b030f976f46277ee211c4a324d5c87555d1084513a1223d3b84416b52bbc28f4b77f3a9d8d0d91dc37d3dbe8af8be98f74674b02f9a38527",
+
+               "3d8cf273d343b9aedccddacb91ad86206737adc86b4a49a7",
+               "bb3a9a0c71c62ef0",
+               "1fde3991c32ce220b5b6666a9234f2fd7bd24b921829fd9cdc6eb4218be9eac9faa9c2351777349128086b6d58776bc86ff2f76ee1b3b2850a318462b8983fa1",
+               "422ce705a46bb52ad928dab6c863166d617c6fc24003633120d91918314bbf464cea7345c3c35f2042f2d6929735d74d7728f22fea618a0b9cf5b1281acb13fb",
+
+               "fbceb5cb646b925be0b92f7f6b493d5e5b16e9159732732a",
+               "2e17b3c7025ae86b",
+               "4c309bc8e1e464fdd2a2b8978645d668d455f7526bd8d7b6716a722f6a900b815c4a73cc30e788065c1dfca7bf5958a6cc5440a5ebe7f8691c20278cde95db764ff8ce8994ece89c",
+               "c02129bdf4bbbd75e71605a00b12c80db6b4e05308e916615011f09147ed915dd1bc67f27f9e027e4e13df36b55464a31c11b4d1fe3d855d89df492e1a7201b995c1ba16a8dbabee",
+
+               "9b162a0df8ad9b61c88676e3d586434570b902f12a2046e0",
+               "ebd6fefe029ad54b",
+               "f4c1c918e77355c8156f0fd778da52bff121ae5f2f44eaf4d2754946d0e10d1f18ce3a0176e69c18b7d20b6e0d0bee5eb5edfe4bd60e4d92adcd86bce72e76f94ee5cbcaa8b01cfddcea2ade575e66ac",
+               "1ff3c8709f403a8eff291aedf50c010df5c5ff64a8b205f1fce68564798897a390db16ee0d053856b75898009731da290fcc119dad987277aacef694872e880c4bb41471063fae05c89f25e4bd0cad6a"
+       };
+
+       /*
+        * From RFC 7539. Each vector consists in 5 values:
+        *    key (hex)
+        *    iv (hex)
+        *    counter (decimal)
+        *    plain (hex)
+        *    cipher (hex)
+        */
+       static string[] KAT_CHACHA20 = {
+               "0000000000000000000000000000000000000000000000000000000000000000",
+               "000000000000000000000000",
+               "0",
+               "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+               "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586",
+
+               "0000000000000000000000000000000000000000000000000000000000000001",
+               "000000000000000000000002",
+               "1",
+               "416e79207375626d697373696f6e20746f20746865204945544620696e74656e6465642062792074686520436f6e7472696275746f7220666f72207075626c69636174696f6e20617320616c6c206f722070617274206f6620616e204945544620496e7465726e65742d4472616674206f722052464320616e6420616e792073746174656d656e74206d6164652077697468696e2074686520636f6e74657874206f6620616e204945544620616374697669747920697320636f6e7369646572656420616e20224945544620436f6e747269627574696f6e222e20537563682073746174656d656e747320696e636c756465206f72616c2073746174656d656e747320696e20494554462073657373696f6e732c2061732077656c6c206173207772697474656e20616e6420656c656374726f6e696320636f6d6d756e69636174696f6e73206d61646520617420616e792074696d65206f7220706c6163652c207768696368206172652061646472657373656420746f",
+               "a3fbf07df3fa2fde4f376ca23e82737041605d9f4f4f57bd8cff2c1d4b7955ec2a97948bd3722915c8f3d337f7d370050e9e96d647b7c39f56e031ca5eb6250d4042e02785ececfa4b4bb5e8ead0440e20b6e8db09d881a7c6132f420e52795042bdfa7773d8a9051447b3291ce1411c680465552aa6c405b7764d5e87bea85ad00f8449ed8f72d0d662ab052691ca66424bc86d2df80ea41f43abf937d3259dc4b2d0dfb48a6c9139ddd7f76966e928e635553ba76c5c879d7b35d49eb2e62b0871cdac638939e25e8a1e0ef9d5280fa8ca328b351c3c765989cbcf3daa8b6ccc3aaf9f3979c92b3720fc88dc95ed84a1be059c6499b9fda236e7e818b04b0bc39c1e876b193bfe5569753f88128cc08aaa9b63d1a16f80ef2554d7189c411f5869ca52c5b83fa36ff216b9c1d30062bebcfd2dc5bce0911934fda79a86f6e698ced759c3ff9b6477338f3da4f9cd8514ea9982ccafb341b2384dd902f3d1ab7ac61dd29c6f21ba5b862f3730e37cfdc4fd806c22f221",
+
+               "1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0",
+               "000000000000000000000002",
+               "42",
+               "2754776173206272696c6c69672c20616e642074686520736c6974687920746f7665730a446964206779726520616e642067696d626c6520696e2074686520776162653a0a416c6c206d696d737920776572652074686520626f726f676f7665732c0a416e6420746865206d6f6d65207261746873206f757467726162652e",
+               "62e6347f95ed87a45ffae7426f27a1df5fb69110044c0d73118effa95b01e5cf166d3df2d721caf9b21e5fb14c616871fd84c54f9d65b283196c7fe4f60553ebf39c6402c42234e32a356b3e764312a61a5532055716ead6962568f87d3f3f7704c6a8d1bcd1bf4d50d6154b6da731b187b58dfd728afa36757a797ac188d1"
+       };
+
+       /*
+        * From: http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf
+        */
+       static string[] KAT_GHASH = {
+
+               "66e94bd4ef8a2c3b884cfa59ca342b2e",
+               "",
+               "",
+               "00000000000000000000000000000000",
+
+               "66e94bd4ef8a2c3b884cfa59ca342b2e",
+               "",
+               "0388dace60b6a392f328c2b971b2fe78",
+               "f38cbb1ad69223dcc3457ae5b6b0f885",
+
+               "b83b533708bf535d0aa6e52980d53b78",
+               "",
+               "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091473f5985",
+               "7f1b32b81b820d02614f8895ac1d4eac",
+
+               "b83b533708bf535d0aa6e52980d53b78",
+               "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+               "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091",
+               "698e57f70e6ecc7fd9463b7260a9ae5f",
+
+               "b83b533708bf535d0aa6e52980d53b78",
+               "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+               "61353b4c2806934a777ff51fa22a4755699b2a714fcdc6f83766e5f97b6c742373806900e49f24b22b097544d4896b424989b5e1ebac0f07c23f4598",
+               "df586bb4c249b92cb6922877e444d37b",
+
+               "b83b533708bf535d0aa6e52980d53b78",
+               "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+               "8ce24998625615b603a033aca13fb894be9112a5c3a211a8ba262a3cca7e2ca701e4a9a4fba43c90ccdcb281d48c7c6fd62875d2aca417034c34aee5",
+               "1c5afe9760d3932f3c9a878aac3dc3de",
+
+               "aae06992acbf52a3e8f4a96ec9300bd7",
+               "",
+               "98e7247c07f0fe411c267e4384b0f600",
+               "e2c63f0ac44ad0e02efa05ab6743d4ce",
+
+               "466923ec9ae682214f2c082badb39249",
+               "",
+               "3980ca0b3c00e841eb06fac4872a2757859e1ceaa6efd984628593b40ca1e19c7d773d00c144c525ac619d18c84a3f4718e2448b2fe324d9ccda2710acade256",
+               "51110d40f6c8fff0eb1ae33445a889f0",
+
+               "466923ec9ae682214f2c082badb39249",
+               "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+               "3980ca0b3c00e841eb06fac4872a2757859e1ceaa6efd984628593b40ca1e19c7d773d00c144c525ac619d18c84a3f4718e2448b2fe324d9ccda2710",
+               "ed2ce3062e4a8ec06db8b4c490e8a268",
+
+               "466923ec9ae682214f2c082badb39249",
+               "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+               "0f10f599ae14a154ed24b36e25324db8c566632ef2bbb34f8347280fc4507057fddc29df9a471f75c66541d4d4dad1c9e93a19a58e8b473fa0f062f7",
+               "1e6a133806607858ee80eaf237064089",
+
+               "466923ec9ae682214f2c082badb39249",
+               "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+               "d27e88681ce3243c4830165a8fdcf9ff1de9a1d8e6b447ef6ef7b79828666e4581e79012af34ddd9e2f037589b292db3e67c036745fa22e7e9b7373b",
+               "82567fb0b4cc371801eadec005968e94",
+
+               "dc95c078a2408989ad48a21492842087",
+               "",
+               "cea7403d4d606b6e074ec5d3baf39d18",
+               "83de425c5edc5d498f382c441041ca92",
+
+               "acbef20579b4b8ebce889bac8732dad7",
+               "",
+               "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662898015ad",
+               "4db870d37cb75fcb46097c36230d1612",
+
+               "acbef20579b4b8ebce889bac8732dad7",
+               "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+               "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662",
+               "8bd0c4d8aacd391e67cca447e8c38f65",
+
+               "acbef20579b4b8ebce889bac8732dad7",
+               "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+               "c3762df1ca787d32ae47c13bf19844cbaf1ae14d0b976afac52ff7d79bba9de0feb582d33934a4f0954cc2363bc73f7862ac430e64abe499f47c9b1f",
+               "75a34288b8c68f811c52b2e9a2f97f63",
+
+               "acbef20579b4b8ebce889bac8732dad7",
+               "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+               "5a8def2f0c9e53f1f75d7853659e2a20eeb2b22aafde6419a058ab4f6f746bf40fc0c3b780f244452da3ebf1c5d82cdea2418997200ef82e44ae7e3f",
+               "d5ffcf6fc5ac4d69722187421a7f170b"
+       };
+
+       static string[] ECDSA_K_P256 = {
+  "04"
++ "60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6"
++ "7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299",
+
+  "C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721"
+       };
+
+       static string[] ECDSA_SIGS_P256 = {
+  "61340C88C3AAEBEB4F6D667F672CA9759A6CCAA9FA8811313039EE4A35471D32",
+  "6D7F147DAC089441BB2E2FE8F7A3FA264B9C475098FDCF6E00D7C996E1B8B7EB",
+
+  "53B2FFF5D1752B2C689DF257C04C40A587FABABB3F6FC2702F1343AF7CA9AA3F",
+  "B9AFB64FDC03DC1A131C7D2386D11E349F070AA432A4ACC918BEA988BF75C74C",
+
+  "EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716",
+  "F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8",
+
+  "0EAFEA039B20E9B42309FB1D89E213057CBF973DC0CFC8F129EDDDC800EF7719",
+  "4861F0491E6998B9455193E34E7B0D284DDD7149A74B95B9261F13ABDE940954",
+
+  "8496A60B5E9B47C825488827E0495B0E3FA109EC4568FD3F8D1097678EB97F00",
+  "2362AB1ADBE2B8ADF9CB9EDAB740EA6049C028114F2460F96554F61FAE3302FE",
+
+  "0CBCC86FD6ABD1D99E703E1EC50069EE5C0B4BA4B9AC60E409E8EC5910D81A89",
+  "01B9D7B73DFAA60D5651EC4591A0136F87653E0FD780C3B1BC872FFDEAE479B1",
+
+  "C37EDB6F0AE79D47C3C27E962FA269BB4F441770357E114EE511F662EC34A692",
+  "C820053A05791E521FCAAD6042D40AEA1D6B1A540138558F47D0719800E18F2D",
+
+  "F1ABB023518351CD71D881567B1EA663ED3EFCF6C5132B354F28D3B0B7D38367",
+  "019F4113742A2B14BD25926B49C649155F267E60D3814B4C0CC84250E46F0083",
+
+  "83910E8B48BB0C74244EBDF7F07A1C5413D61472BD941EF3920E623FBCCEBEB6",
+  "8DDBEC54CF8CD5874883841D712142A56A8D0F218F5003CB0296B6B509619F2C",
+
+  "461D93F31B6540894788FD206C07CFA0CC35F46FA3C91816FFF1040AD1581A04",
+  "39AF9F15DE0DB8D97E72719C74820D304CE5226E32DEDAE67519E840D1194E55"
+       };
+
+       static string[] ECDSA_K_P384 = {
+               "04"
+               + "EC3A4E415B4E19A4568618029F427FA5DA9A8BC4AE92E02E"
+               + "06AAE5286B300C64DEF8F0EA9055866064A254515480BC13"
+               + "8015D9B72D7D57244EA8EF9AC0C621896708A59367F9DFB9"
+               + "F54CA84B3F1C9DB1288B231C3AE0D4FE7344FD2533264720",
+
+                 "6B9D3DAD2E1B8C1C05B19875B6659F4DE23C3B667BF297BA"
+               + "9AA47740787137D896D5724E4C70A825F872C9EA60D2EDF5"
+       };
+
+       static string[] ECDSA_SIGS_P384 = {
+                 "EC748D839243D6FBEF4FC5C4859A7DFFD7F3ABDDF7201454"
+               + "0C16D73309834FA37B9BA002899F6FDA3A4A9386790D4EB2",
+                 "A3BCFA947BEEF4732BF247AC17F71676CB31A847B9FF0CBC"
+               + "9C9ED4C1A5B3FACF26F49CA031D4857570CCB5CA4424A443",
+
+                 "42356E76B55A6D9B4631C865445DBE54E056D3B3431766D0"
+               + "509244793C3F9366450F76EE3DE43F5A125333A6BE060122",
+                 "9DA0C81787064021E78DF658F2FBB0B042BF304665DB721F"
+               + "077A4298B095E4834C082C03D83028EFBF93A3C23940CA8D",
+
+                 "21B13D1E013C7FA1392D03C5F99AF8B30C570C6F98D4EA8E"
+               + "354B63A21D3DAA33BDE1E888E63355D92FA2B3C36D8FB2CD",
+                 "F3AA443FB107745BF4BD77CB3891674632068A10CA67E3D4"
+               + "5DB2266FA7D1FEEBEFDC63ECCD1AC42EC0CB8668A4FA0AB0",
+
+                 "94EDBB92A5ECB8AAD4736E56C691916B3F88140666CE9FA7"
+               + "3D64C4EA95AD133C81A648152E44ACF96E36DD1E80FABE46",
+                 "99EF4AEB15F178CEA1FE40DB2603138F130E740A19624526"
+               + "203B6351D0A3A94FA329C145786E679E7B82C71A38628AC8",
+
+                 "ED0959D5880AB2D869AE7F6C2915C6D60F96507F9CB3E047"
+               + "C0046861DA4A799CFE30F35CC900056D7C99CD7882433709",
+                 "512C8CCEEE3890A84058CE1E22DBC2198F42323CE8ACA913"
+               + "5329F03C068E5112DC7CC3EF3446DEFCEB01A45C2667FDD5",
+
+                 "4BC35D3A50EF4E30576F58CD96CE6BF638025EE624004A1F"
+               + "7789A8B8E43D0678ACD9D29876DAF46638645F7F404B11C7",
+                 "D5A6326C494ED3FF614703878961C0FDE7B2C278F9A65FD8"
+               + "C4B7186201A2991695BA1C84541327E966FA7B50F7382282",
+
+                 "E8C9D0B6EA72A0E7837FEA1D14A1A9557F29FAA45D3E7EE8"
+               + "88FC5BF954B5E62464A9A817C47FF78B8C11066B24080E72",
+                 "07041D4A7A0379AC7232FF72E6F77B6DDB8F09B16CCE0EC3"
+               + "286B2BD43FA8C6141C53EA5ABEF0D8231077A04540A96B66",
+
+                 "6D6DEFAC9AB64DABAFE36C6BF510352A4CC27001263638E5"
+               + "B16D9BB51D451559F918EEDAF2293BE5B475CC8F0188636B",
+                 "2D46F3BECBCC523D5F1A1256BF0C9B024D879BA9E838144C"
+               + "8BA6BAEB4B53B47D51AB373F9845C0514EEFB14024787265",
+
+                 "8203B63D3C853E8D77227FB377BCF7B7B772E97892A80F36"
+               + "AB775D509D7A5FEB0542A7F0812998DA8F1DD3CA3CF023DB",
+                 "DDD0760448D42D8A43AF45AF836FCE4DE8BE06B485E9B61B"
+               + "827C2F13173923E06A739F040649A667BF3B828246BAA5A5",
+
+                 "A0D5D090C9980FAF3C2CE57B7AE951D31977DD11C775D314"
+               + "AF55F76C676447D06FB6495CD21B4B6E340FC236584FB277",
+                 "976984E59B4C77B0E8E4460DCA3D9F20E07B9BB1F63BEEFA"
+               + "F576F6B2E8B224634A2092CD3792E0159AD9CEE37659C736"
+       };
+
+       static string[] ECDSA_K_P521 = {
+  "04"
++ "01894550D0785932E00EAA23B694F213F8C3121F86DC97A04E5A7167DB4E5BCD37"
++ "1123D46E45DB6B5D5370A7F20FB633155D38FFA16D2BD761DCAC474B9A2F5023A4"
++ "00493101C962CD4D2FDDF782285E64584139C2F91B47F87FF82354D6630F746A28"
++ "A0DB25741B5B34A828008B22ACC23F924FAAFBD4D33F81EA66956DFEAA2BFDFCF5",
+
+  "00FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75CA"
++ "A896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83538"
+       };
+
+       static string[] ECDSA_SIGS_P521 = {
+  "00343B6EC45728975EA5CBA6659BBB6062A5FF89EEA58BE3C80B619F322C87910F"
++ "E092F7D45BB0F8EEE01ED3F20BABEC079D202AE677B243AB40B5431D497C55D75D",
+  "00E7B0E675A9B24413D448B8CC119D2BF7B2D2DF032741C096634D6D65D0DBE3D5"
++ "694625FB9E8104D3B842C1B0E2D0B98BEA19341E8676AEF66AE4EBA3D5475D5D16",
+
+  "01776331CFCDF927D666E032E00CF776187BC9FDD8E69D0DABB4109FFE1B5E2A30"
++ "715F4CC923A4A5E94D2503E9ACFED92857B7F31D7152E0F8C00C15FF3D87E2ED2E",
+  "0050CB5265417FE2320BBB5A122B8E1A32BD699089851128E360E620A30C7E17BA"
++ "41A666AF126CE100E5799B153B60528D5300D08489CA9178FB610A2006C254B41F",
+
+  "01511BB4D675114FE266FC4372B87682BAECC01D3CC62CF2303C92B3526012659D"
++ "16876E25C7C1E57648F23B73564D67F61C6F14D527D54972810421E7D87589E1A7",
+  "004A171143A83163D6DF460AAF61522695F207A58B95C0644D87E52AA1A347916E"
++ "4F7A72930B1BC06DBE22CE3F58264AFD23704CBB63B29B931F7DE6C9D949A7ECFC",
+
+  "01EA842A0E17D2DE4F92C15315C63DDF72685C18195C2BB95E572B9C5136CA4B4B"
++ "576AD712A52BE9730627D16054BA40CC0B8D3FF035B12AE75168397F5D50C67451",
+  "01F21A3CEE066E1961025FB048BD5FE2B7924D0CD797BABE0A83B66F1E35EEAF5F"
++ "DE143FA85DC394A7DEE766523393784484BDF3E00114A1C857CDE1AA203DB65D61",
+
+  "00C328FAFCBD79DD77850370C46325D987CB525569FB63C5D3BC53950E6D4C5F17"
++ "4E25A1EE9017B5D450606ADD152B534931D7D4E8455CC91F9B15BF05EC36E377FA",
+  "00617CCE7CF5064806C467F678D3B4080D6F1CC50AF26CA209417308281B68AF28"
++ "2623EAA63E5B5C0723D8B8C37FF0777B1A20F8CCB1DCCC43997F1EE0E44DA4A67A",
+
+  "013BAD9F29ABE20DE37EBEB823C252CA0F63361284015A3BF430A46AAA80B87B06"
++ "93F0694BD88AFE4E661FC33B094CD3B7963BED5A727ED8BD6A3A202ABE009D0367",
+  "01E9BB81FF7944CA409AD138DBBEE228E1AFCC0C890FC78EC8604639CB0DBDC90F"
++ "717A99EAD9D272855D00162EE9527567DD6A92CBD629805C0445282BBC916797FF",
+
+  "01C7ED902E123E6815546065A2C4AF977B22AA8EADDB68B2C1110E7EA44D42086B"
++ "FE4A34B67DDC0E17E96536E358219B23A706C6A6E16BA77B65E1C595D43CAE17FB",
+  "0177336676304FCB343CE028B38E7B4FBA76C1C1B277DA18CAD2A8478B2A9A9F5B"
++ "EC0F3BA04F35DB3E4263569EC6AADE8C92746E4C82F8299AE1B8F1739F8FD519A4",
+
+  "000E871C4A14F993C6C7369501900C4BC1E9C7B0B4BA44E04868B30B41D8071042"
++ "EB28C4C250411D0CE08CD197E4188EA4876F279F90B3D8D74A3C76E6F1E4656AA8",
+  "00CD52DBAA33B063C3A6CD8058A1FB0A46A4754B034FCC644766CA14DA8CA5CA9F"
++ "DE00E88C1AD60CCBA759025299079D7A427EC3CC5B619BFBC828E7769BCD694E86",
+
+  "014BEE21A18B6D8B3C93FAB08D43E739707953244FDBE924FA926D76669E7AC8C8"
++ "9DF62ED8975C2D8397A65A49DCC09F6B0AC62272741924D479354D74FF6075578C",
+  "0133330865C067A0EAF72362A65E2D7BC4E461E8C8995C3B6226A21BD1AA78F0ED"
++ "94FE536A0DCA35534F0CD1510C41525D163FE9D74D134881E35141ED5E8E95B979",
+
+  "013E99020ABF5CEE7525D16B69B229652AB6BDF2AFFCAEF38773B4B7D08725F10C"
++ "DB93482FDCC54EDCEE91ECA4166B2A7C6265EF0CE2BD7051B7CEF945BABD47EE6D",
+  "01FBD0013C674AA79CB39849527916CE301C66EA7CE8B80682786AD60F98F7E78A"
++ "19CA69EFF5C57400E3B3A0AD66CE0978214D13BAF4E9AC60752F7B155E2DE4DCE3"
+       };
+}
diff --git a/Tests/TestEC.cs b/Tests/TestEC.cs
new file mode 100644 (file)
index 0000000..6049b07
--- /dev/null
@@ -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 (file)
index 0000000..8d4c65a
--- /dev/null
@@ -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 (file)
index 0000000..9392847
--- /dev/null
@@ -0,0 +1,694 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+/*
+ * A simple JSON parser.
+ *
+ * A JSON value is returned as:
+ *
+ *   - null, if the value is a JSON null;
+ *
+ *   - a string, if the value is a JSON string, a JSON number or a
+ *     JSON boolean;
+ *
+ *   - an IDictionary<string, object>, if the value is a JSON object;
+ *
+ *   - an array (object[]), if the value is a JSON array.
+ *
+ * This parser is lenient with numbers, in that it will gleefully
+ * accumulate digits, dots, minus sign, plus sign, lowercase 'e'
+ * and uppercase 'E' characters in any order.
+ */
+
+public static class JSON {
+
+       /*
+        * Parse a source stream as a JSON object.
+        */
+       public static object Parse(Stream src)
+       {
+               return Parse(new StreamReader(src));
+       }
+
+       /*
+        * Parse a source stream as a JSON object.
+        */
+       public static object Parse(TextReader tr)
+       {
+               int cp = NextNonWS(tr, ' ');
+               object val;
+               cp = ReadValue(tr, cp, out val);
+               while (cp >= 0) {
+                       if (!IsWS(cp)) {
+                               throw new Exception(
+                                       "Trailing garbage after JSON value");
+                       }
+                       cp = tr.Read();
+               }
+               return val;
+       }
+
+       /*
+        * Encode a JSON object onto a stream.
+        */
+       public static void Encode(object obj, Stream dst)
+       {
+               TextWriter tw = new StreamWriter(dst);
+               Encode(obj, tw);
+               tw.Flush();
+       }
+
+       /*
+        * Encode a JSON object onto a stream.
+        */
+       public static void Encode(object obj, TextWriter tw)
+       {
+               EncodeValue(0, obj, tw);
+               tw.WriteLine();
+       }
+
+       /*
+        * Get a value by path. If the value is present, then 'val'
+        * is set to that value (which may be null) and true is returned;
+        * otherwise, 'val' is set to null and false is written.
+        *
+        * An exception is still thrown if one of the upper path elements
+        * does not have the expected type.
+        */
+       public static bool TryGet(object obj, string path, out object val)
+       {
+               int n = path.Length;
+               int p = 0;
+               while (p < n) {
+                       int q = path.IndexOf('/', p);
+                       if (q < 0) {
+                               q = n;
+                       }
+                       IDictionary<string, object> d =
+                               obj as IDictionary<string, object>;
+                       if (d == null) {
+                               throw new Exception(string.Format(
+                                       "Path '{0}': not an object",
+                                       path.Substring(0, p)));
+                       }
+                       string name = path.Substring(p, q - p);
+                       if (!d.ContainsKey(name)) {
+                               val = null;
+                               return false;
+                       }
+                       obj = d[name];
+                       p = q + 1;
+               }
+               val = obj;
+               return true;
+       }
+
+       /*
+        * Get a value by path.
+        */
+       public static object Get(object obj, string path)
+       {
+               object val;
+               if (!TryGet(obj, path, out val)) {
+                       throw new Exception("No such value: " + path);
+               }
+               return val;
+       }
+
+       /*
+        * Try to get a value by path; value (if present) should be a
+        * string.
+        */
+       public static bool TryGetString(object obj, string path, out string val)
+       {
+               object gv;
+               if (!TryGet(obj, path, out gv)) {
+                       val = null;
+                       return false;
+               }
+               if (!(gv is string)) {
+                       throw new Exception("Value at " + path
+                               + " is not a string");
+               }
+               val = gv as string;
+               return true;
+       }
+
+       /*
+        * Get a value by path; value should be a string.
+        */
+       public static string GetString(object obj, string path)
+       {
+               string str;
+               if (!TryGetString(obj, path, out str)) {
+                       throw new Exception("No such value: " + path);
+               }
+               return str;
+       }
+
+       /*
+        * Try to get a value by path; value should be an array.
+        */
+       public static bool TryGetArray(object obj, string path,
+               out object[] val)
+       {
+               object gv;
+               if (!TryGet(obj, path, out gv)) {
+                       val = null;
+                       return false;
+               }
+               val = gv as object[];
+               if (val == null) {
+                       throw new Exception("Value at " + path
+                               + " is not an array");
+               }
+               return true;
+       }
+
+       /*
+        * Get a value by path; value should be an array.
+        */
+       public static object[] GetArray(object obj, string path)
+       {
+               object[] a;
+               if (!TryGetArray(obj, path, out a)) {
+                       throw new Exception("No such value: " + path);
+               }
+               return a;
+       }
+
+       /*
+        * Try to get a value by path; if present, value should be an
+        * array, whose elements are all strings. A new, properly typed
+        * array is returned, containing the strings.
+        */
+       public static bool TryGetStringArray(object obj, string path,
+               out string[] a)
+       {
+               object[] g;
+               if (!TryGetArray(obj, path, out g)) {
+                       a = null;
+                       return false;
+               }
+               string[] r = new string[g.Length];
+               for (int i = 0; i < g.Length; i ++) {
+                       string s = g[i] as string;
+                       if (s == null) {
+                               throw new Exception(string.Format("Element {0}"
+                                       + " in array {1} is not a string",
+                                       i, path));
+                       }
+                       r[i] = s;
+               }
+               a = r;
+               return true;
+       }
+
+       /*
+        * Get a value by path; value should be an array, whose
+        * elements are all strings. A new, properly typed array is
+        * returned, containing the strings.
+        */
+       public static string[] GetStringArray(object obj, string path)
+       {
+               string[] a;
+               if (!TryGetStringArray(obj, path, out a)) {
+                       throw new Exception("No such value: " + path);
+               }
+               return a;
+       }
+
+       /*
+        * Try to get a value by path; value should a boolean.
+        */
+       public static bool TryGetBool(object obj, string path, out bool val)
+       {
+               object gv;
+               if (!TryGet(obj, path, out gv)) {
+                       val = false;
+                       return false;
+               }
+               if (gv is bool) {
+                       val = (bool)gv;
+                       return true;
+               } else if (gv is string) {
+                       switch (gv as string) {
+                       case "true":   val = true; return true;
+                       case "false":  val = false; return true;
+                       }
+               }
+               throw new Exception("Value at " + path + " is not a boolean");
+       }
+
+       /*
+        * Get a value by path; value should a boolean.
+        */
+       public static bool GetBool(object obj, string path)
+       {
+               bool v;
+               if (!TryGetBool(obj, path, out v)) {
+                       throw new Exception("No such value: " + path);
+               }
+               return v;
+       }
+
+       /*
+        * Try to get a value by path; value should an integer.
+        */
+       public static bool TryGetInt32(object obj, string path, out int val)
+       {
+               object gv;
+               if (!TryGet(obj, path, out gv)) {
+                       val = 0;
+                       return false;
+               }
+               if (gv is int) {
+                       val = (int)gv;
+                       return true;
+               } else if (gv is uint) {
+                       uint x = (uint)gv;
+                       if (x <= (uint)Int32.MaxValue) {
+                               val = (int)x;
+                               return true;
+                       }
+               } else if (gv is long) {
+                       long x = (long)gv;
+                       if (x >= (long)Int32.MinValue
+                               && x <= (long)Int32.MaxValue)
+                       {
+                               val = (int)x;
+                               return true;
+                       }
+               } else if (gv is ulong) {
+                       ulong x = (ulong)gv;
+                       if (x <= (ulong)Int32.MaxValue) {
+                               val = (int)x;
+                               return true;
+                       }
+               } else if (gv is string) {
+                       int x;
+                       if (Int32.TryParse((string)gv, out x)) {
+                               val = x;
+                               return true;
+                       }
+               }
+               throw new Exception("Value at " + path + " is not a boolean");
+       }
+
+       /*
+        * Get a value by path; value should an integer.
+        */
+       public static int GetInt32(object obj, string path)
+       {
+               int v;
+               if (!TryGetInt32(obj, path, out v)) {
+                       throw new Exception("No such value: " + path);
+               }
+               return v;
+       }
+
+       /*
+        * Try to get a value by path; value should be an object map.
+        */
+       public static bool TryGetObjectMap(object obj, string path,
+               out IDictionary<string, object> val)
+       {
+               object gv;
+               if (!TryGet(obj, path, out gv)) {
+                       val = null;
+                       return false;
+               }
+               val = gv as IDictionary<string, object>;
+               if (val == null) {
+                       throw new Exception("Value at " + path
+                               + " is not an object map");
+               }
+               return true;
+       }
+
+       /*
+        * Get a value by path; value should be an object map.
+        */
+       public static IDictionary<string, object> GetObjectMap(
+               object obj, string path)
+       {
+               IDictionary<string, object> v;
+               if (!TryGetObjectMap(obj, path, out v)) {
+                       throw new Exception("No such value: " + path);
+               }
+               return v;
+       }
+
+       static void EncodeValue(int indent, object obj, TextWriter tw)
+       {
+               if (obj == null) {
+                       tw.Write("null");
+                       return;
+               }
+               if (obj is bool) {
+                       tw.Write((bool)obj ? "true" : "false");
+                       return;
+               }
+               if (obj is string) {
+                       EncodeString((string)obj, tw);
+                       return;
+               }
+               if (obj is int || obj is uint || obj is long || obj is ulong) {
+                       tw.Write(obj.ToString());
+                       return;
+               }
+               if (obj is Array) {
+                       tw.Write("[");
+                       Array a = (Array)obj;
+                       for (int i = 0; i < a.Length; i ++) {
+                               if (i != 0) {
+                                       tw.Write(",");
+                               }
+                               tw.WriteLine();
+                               Indent(indent + 1, tw);
+                               EncodeValue(indent + 1, a.GetValue(i), tw);
+                       }
+                       tw.WriteLine();
+                       Indent(indent, tw);
+                       tw.Write("]");
+                       return;
+               }
+               if (obj is IDictionary<string, object>) {
+                       tw.Write("{");
+                       IDictionary<string, object> d =
+                               (IDictionary<string, object>)obj;
+                       bool first = true;
+                       foreach (string name in d.Keys) {
+                               if (first) {
+                                       first = false;
+                               } else {
+                                       tw.Write(",");
+                               }
+                               tw.WriteLine();
+                               Indent(indent + 1, tw);
+                               EncodeString(name, tw);
+                               tw.Write(" : ");
+                               EncodeValue(indent + 1, d[name], tw);
+                       }
+                       tw.WriteLine();
+                       Indent(indent, tw);
+                       tw.Write("}");
+                       return;
+               }
+               throw new Exception("Unknown value type: "
+                       + obj.GetType().FullName);
+       }
+
+       static void Indent(int indent, TextWriter tw)
+       {
+               while (indent -- > 0) {
+                       tw.Write("  ");
+               }
+       }
+
+       static void EncodeString(string str, TextWriter tw)
+       {
+               tw.Write('\"');
+               foreach (char c in str) {
+                       if (c >= 32 && c <= 126) {
+                               if (c == '\\' || c == '"') {
+                                       tw.Write('\\');
+                               }
+                               tw.Write(c);
+                       } else {
+                               switch (c) {
+                               case '\b':
+                                       tw.Write("\\b");
+                                       break;
+                               case '\f':
+                                       tw.Write("\\f");
+                                       break;
+                               case '\n':
+                                       tw.Write("\\n");
+                                       break;
+                               case '\r':
+                                       tw.Write("\\r");
+                                       break;
+                               case '\t':
+                                       tw.Write("\\t");
+                                       break;
+                               default:
+                                       tw.Write("\\u{0:X4}", (int)c);
+                                       break;
+                               }
+                       }
+               }
+               tw.Write('\"');
+       }
+
+       /*
+        * Read a value, that starts with the provided character. The
+        * value is written in 'val'. Returned value is the next
+        * character in the stream, or a synthetic space if the next
+        * character was not read.
+        */
+       static int ReadValue(TextReader tr, int cp, out object val)
+       {
+               switch (cp) {
+               case '"':
+                       val = ReadString(tr);
+                       return ' ';
+               case '{':
+                       val = ReadObject(tr);
+                       return ' ';
+               case '[':
+                       val = ReadArray(tr);
+                       return ' ';
+               case 't':
+                       CheckKeyword(tr, "true");
+                       val = "true";
+                       return ' ';
+               case 'f':
+                       CheckKeyword(tr, "false");
+                       val = "false";
+                       return ' ';
+               case 'n':
+                       CheckKeyword(tr, "null");
+                       val = null;
+                       return ' ';
+               case '-':
+               case '0': case '1': case '2': case '3': case '4':
+               case '5': case '6': case '7': case '8': case '9':
+                       StringBuilder sb = new StringBuilder();
+                       sb.Append((char)cp);
+                       cp = ReadNumber(tr, sb);
+                       val = sb.ToString();
+                       return cp;
+               default:
+                       throw Unexpected(cp);
+               }
+       }
+
+       static string ReadString(TextReader tr)
+       {
+               StringBuilder sb = new StringBuilder();
+               bool lcwb = false;
+               for (;;) {
+                       int cp = Next(tr);
+                       if (lcwb) {
+                               lcwb = false;
+                               switch (cp) {
+                               case '"': case '\\': case '/':
+                                       sb.Append((char)cp);
+                                       break;
+                               case 'b':
+                                       sb.Append('\b');
+                                       break;
+                               case 'f':
+                                       sb.Append('\f');
+                                       break;
+                               case 'n':
+                                       sb.Append('\n');
+                                       break;
+                               case 'r':
+                                       sb.Append('\r');
+                                       break;
+                               case 't':
+                                       sb.Append('\t');
+                                       break;
+                               case 'u':
+                                       sb.Append(ReadUnicodeEscape(tr));
+                                       break;
+                               default:
+                                       throw Unexpected(cp);
+                               }
+                       } else {
+                               if (cp == '\\') {
+                                       lcwb = true;
+                               } else if (cp == '"') {
+                                       return sb.ToString();
+                               } else if (cp <= 0x1F) {
+                                       throw Unexpected(cp);
+                               } else {
+                                       sb.Append((char)cp);
+                               }
+                       }
+               }
+       }
+
+       static char ReadUnicodeEscape(TextReader tr)
+       {
+               int acc = 0;
+               for (int i = 0; i < 4; i ++) {
+                       int cp = Next(tr);
+                       if (cp >= '0' && cp <= '9') {
+                               cp -= '0';
+                       } else if (cp >= 'A' && cp <= 'F') {
+                               cp -= 'A' - 10;
+                       } else if (cp >= 'a' && cp <= 'f') {
+                               cp -= 'a' - 10;
+                       } else {
+                               throw Unexpected(cp);
+                       }
+                       acc = (acc << 4) + cp;
+               }
+               return (char)acc;
+       }
+
+       static IDictionary<string, object> ReadObject(TextReader tr)
+       {
+               IDictionary<string, object> r =
+                       new SortedDictionary<string, object>(
+                               StringComparer.Ordinal);
+               int cp = NextNonWS(tr, ' ');
+               if (cp == '}') {
+                       return r;
+               }
+               for (;;) {
+                       if (cp != '"') {
+                               throw Unexpected(cp);
+                       }
+                       string name = ReadString(tr);
+                       cp = NextNonWS(tr, ' ');
+                       if (cp != ':') {
+                               throw Unexpected(cp);
+                       }
+                       if (r.ContainsKey(name)) {
+                               throw new Exception(string.Format(
+                                       "duplicate key '{0}' in object",
+                                       name));
+                       }
+                       object val;
+                       cp = NextNonWS(tr, ' ');
+                       cp = ReadValue(tr, cp, out val);
+                       r[name] = val;
+                       cp = NextNonWS(tr, cp);
+                       if (cp == '}') {
+                               return r;
+                       }
+                       if (cp != ',') {
+                               throw Unexpected(cp);
+                       }
+                       cp = NextNonWS(tr, ' ');
+               }
+       }
+
+       static object[] ReadArray(TextReader tr)
+       {
+               List<object> r = new List<object>();
+               int cp = NextNonWS(tr, ' ');
+               if (cp == ']') {
+                       return r.ToArray();
+               }
+               for (;;) {
+                       object val;
+                       cp = ReadValue(tr, cp, out val);
+                       r.Add(val);
+                       cp = NextNonWS(tr, cp);
+                       if (cp == ']') {
+                               return r.ToArray();
+                       }
+                       if (cp != ',') {
+                               throw Unexpected(cp);
+                       }
+                       cp = NextNonWS(tr, ' ');
+               }
+       }
+
+       static int ReadNumber(TextReader tr, StringBuilder sb)
+       {
+               int cp;
+               for (;;) {
+                       cp = tr.Read();
+                       switch (cp) {
+                       case '0': case '1': case '2': case '3': case '4':
+                       case '5': case '6': case '7': case '8': case '9':
+                       case '.': case '-': case '+': case 'e': case 'E':
+                               sb.Append((char)cp);
+                               break;
+                       default:
+                               return cp;
+                       }
+               }
+       }
+
+       static void CheckKeyword(TextReader tr, string str)
+       {
+               int n = str.Length;
+               for (int i = 1; i < n; i ++) {
+                       int cp = Next(tr);
+                       if (cp != (int)str[i]) {
+                               throw Unexpected(cp);
+                       }
+               }
+       }
+
+       static bool IsWS(int cp)
+       {
+               return cp == 9 || cp == 10 || cp == 13 || cp == 32;
+       }
+
+       static int Next(TextReader tr)
+       {
+               int cp = tr.Read();
+               if (cp < 0) {
+                       throw new EndOfStreamException();
+               }
+               return cp;
+       }
+
+       static int NextNonWS(TextReader tr, int cp)
+       {
+               while (IsWS(cp)) {
+                       cp = Next(tr);
+               }
+               return cp;
+       }
+
+       static Exception Unexpected(int cp)
+       {
+               return new Exception(string.Format(
+                       "Unexpected character U+{0:X4}", cp));
+       }
+}
diff --git a/Twrch/MergeStream.cs b/Twrch/MergeStream.cs
new file mode 100644 (file)
index 0000000..4a5ac29
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.IO;
+
+/*
+ * This class merges two underlying streams (one for reading, the other
+ * for writing) into a single Stream object. It can also optionally dump
+ * all read and written bytes, in hexadecimal, on an provided text
+ * stream (for debugging purposes).
+ */
+
+internal class MergeStream : Stream {
+
+       Stream subIn, subOut;
+
+       internal TextWriter Debug {
+               get; set;
+       }
+
+       internal MergeStream(Stream subIn, Stream subOut)
+       {
+               this.subIn = subIn;
+               this.subOut = subOut;
+       }
+
+       public override int ReadByte()
+       {
+               int x = subIn.ReadByte();
+               if (Debug != null) {
+                       if (x >= 0) {
+                               Debug.WriteLine("recv:");
+                               Debug.WriteLine("   {0:x2}", x);
+                       } else {
+                               Debug.WriteLine("recv: EOF");
+                       }
+               }
+               return x;
+       }
+
+       public override int Read(byte[] buf, int off, int len)
+       {
+               int rlen = subIn.Read(buf, off, len);
+               if (Debug != null) {
+                       if (rlen <= 0) {
+                               Debug.WriteLine("recv: EOF");
+                       } else {
+                               Debug.Write("recv:");
+                               for (int i = 0; i < rlen; i ++) {
+                                       if ((i & 15) == 0) {
+                                               Debug.WriteLine();
+                                               Debug.Write("   ");
+                                       } else if ((i & 7) == 0) {
+                                               Debug.Write("  ");
+                                       } else {
+                                               Debug.Write(" ");
+                                       }
+                                       Debug.Write("{0:x2}", buf[i]);
+                               }
+                               Debug.WriteLine();
+                       }
+               }
+               return rlen;
+       }
+
+       public override void WriteByte(byte x)
+       {
+               if (Debug != null) {
+                       Debug.WriteLine("send:");
+                       Debug.WriteLine("   {0:x2}", x);
+               }
+               subOut.WriteByte(x);
+       }
+
+       public override void Write(byte[] buf, int off, int len)
+       {
+               if (Debug != null) {
+                       Debug.Write("send:");
+                       for (int i = 0; i < len; i ++) {
+                               if ((i & 15) == 0) {
+                                       Debug.WriteLine();
+                                       Debug.Write("   ");
+                               } else if ((i & 7) == 0) {
+                                       Debug.Write("  ");
+                               } else {
+                                       Debug.Write(" ");
+                               }
+                               Debug.Write("{0:x2}", buf[i]);
+                       }
+                       Debug.WriteLine();
+               }
+               subOut.Write(buf, off, len);
+       }
+
+       public override void Flush()
+       {
+               subOut.Flush();
+       }
+
+       public override void Close()
+       {
+               Exception ex1 = null, ex2 = null;
+               try {
+                       subIn.Close();
+               } catch (Exception ex) {
+                       ex1 = ex;
+               }
+               try {
+                       subOut.Close();
+               } catch (Exception ex) {
+                       ex2 = ex;
+               }
+               if (ex2 != null) {
+                       throw ex2;
+               } else if (ex1 != null) {
+                       throw ex1;
+               }
+       }
+
+       public override long Seek(long off, SeekOrigin origin)
+       {
+               throw new NotSupportedException();
+       }
+
+       public override void SetLength(long len)
+       {
+               throw new NotSupportedException();
+       }
+
+       public override bool CanRead {
+               get {
+                       return subIn.CanRead;
+               }
+       }
+
+       public override bool CanWrite {
+               get {
+                       return subOut.CanWrite;
+               }
+       }
+
+       public override bool CanSeek {
+               get {
+                       return false;
+               }
+       }
+
+       public override long Length {
+               get {
+                       throw new NotSupportedException();
+               }
+       }
+
+       public override long Position {
+               get {
+                       throw new NotSupportedException();
+               }
+               set {
+                       throw new NotSupportedException();
+               }
+       }
+}
diff --git a/Twrch/Twrch.cs b/Twrch/Twrch.cs
new file mode 100644 (file)
index 0000000..6692d53
--- /dev/null
@@ -0,0 +1,1336 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Text;
+
+using Asn1;
+using Crypto;
+using SSLTLS;
+using XKeys;
+
+/*
+ * This is the main Twrch class implementation: it provides the entry
+ * point for the command-line application.
+ */
+
+public class Twrch {
+
+       public static void Main(string[] args)
+       {
+               try {
+                       new Twrch().Run(args);
+               } catch (Exception e) {
+                       Console.WriteLine(e.ToString());
+                       Environment.Exit(1);
+               }
+       }
+
+       bool trace;
+       object conf;
+       string commandFile;
+       string commandArgs;
+       bool commandVerbose;
+       string chainRSAFile;
+       byte[][] chainRSA;
+       string skeyRSAFile;
+       IPrivateKey skeyRSA;
+       string chainECFile;
+       byte[][] chainEC;
+       string skeyECFile;
+       IPrivateKey skeyEC;
+       int[] versions;
+       int versionMin;
+       int versionMax;
+       int[] cipherSuites;
+       int[] hashAndSigns;
+       int[] curves;
+       bool noCloseNotify;
+       object[] tests;
+       IDictionary<string, object> testsByName;
+
+       int totalTests;
+       int totalSuccess;
+       int totalFailures;
+
+       void Run(string[] args)
+       {
+               List<string> r = new List<string>();
+               string confName = null;
+               int doEnum = 0;
+               foreach (string a in args) {
+                       string b = a.ToLowerInvariant();
+                       switch (b) {
+                       case "-trace":
+                               trace = true;
+                               break;
+                       case "-enum":
+                               doEnum = 1;
+                               break;
+                       case "-noenum":
+                               doEnum = -1;
+                               break;
+                       case "-cv":
+                               commandVerbose = true;
+                               break;
+                       default:
+                               if (confName == null) {
+                                       confName = a;
+                               } else {
+                                       r.Add(a);
+                               }
+                               break;
+                       }
+               }
+               if (confName == null) {
+                       Usage();
+               }
+               string[] testNames = r.ToArray();
+               conf = ReadConfig(confName);
+               if (doEnum == 0) {
+                       doEnum = (testNames.Length == 0) ? 1 : -1;
+               }
+               commandFile = JSON.GetString(conf, "commandFile");
+               commandArgs = JSON.GetString(conf, "commandArgs");
+               chainRSAFile = JSON.GetString(conf, "chainRSA");
+               chainECFile = JSON.GetString(conf, "chainEC");
+               skeyRSAFile = JSON.GetString(conf, "skeyRSA");
+               skeyECFile = JSON.GetString(conf, "skeyEC");
+               chainRSA = DecodeChain(chainRSAFile);
+               skeyRSA = DecodePrivateKey(skeyRSAFile);
+               chainEC = DecodeChain(chainECFile);
+               skeyEC = DecodePrivateKey(skeyECFile);
+               versions = GetVersions();
+               if (versions.Length == 0) {
+                       throw new Exception("Bad config: no versions");
+               }
+               versionMin = Int32.MaxValue;
+               versionMax = -1;
+               foreach (int v in versions) {
+                       versionMin = Math.Min(v, versionMin);
+                       versionMax = Math.Max(v, versionMax);
+               }
+               cipherSuites = GetCipherSuites();
+               if (cipherSuites.Length == 0) {
+                       throw new Exception("Bad config: no cipher suites");
+               }
+               hashAndSigns = GetHashAndSigns();
+               if (hashAndSigns.Length == 0) {
+                       throw new Exception("Bad config: no hash-and-signs");
+               }
+               curves = GetCurves();
+               noCloseNotify = JSON.GetBool(conf, "noCloseNotify");
+               tests = JSON.GetArray(conf, "tests");
+               testsByName = new SortedDictionary<string, object>(
+                       StringComparer.Ordinal);
+               foreach (object obj in tests) {
+                       string name = JSON.GetString(obj, "name");
+                       testsByName[name] = obj;
+               }
+
+               totalTests = 0;
+               totalSuccess = 0;
+               totalFailures = 0;
+               if (doEnum > 0) {
+                       totalTests += ComputeTotalEnum();
+               }
+               if (testNames.Length == 0) {
+                       foreach (object obj in tests) {
+                               totalTests += GetNumTests(obj);
+                       }
+               } else {
+                       foreach (string name in testNames) {
+                               bool client;
+                               int version, suite, curve, hs;
+                               if (StringToTEnum(name, out client, out version,
+                                       out suite, out curve, out hs))
+                               {
+                                       totalTests ++;
+                                       continue;
+                               }
+                               if (name.EndsWith("_client")
+                                       || name.EndsWith("_server"))
+                               {
+                                       totalTests ++;
+                               } else {
+                                       totalTests += GetNumTests(
+                                               testsByName[name]);
+                               }
+                       }
+               }
+
+               if (doEnum > 0) {
+                       RunEnum();
+               }
+               if (testNames.Length == 0) {
+                       foreach (object obj in tests) {
+                               RunTest(obj);
+                       }
+               } else {
+                       foreach (string name in testNames) {
+                               bool client;
+                               int version, suite, curve, hs;
+                               if (StringToTEnum(name, out client, out version,
+                                       out suite, out curve, out hs))
+                               {
+                                       RunEnum(client, version,
+                                               suite, curve, hs);
+                                       continue;
+                               }
+                               if (name.EndsWith("_client")) {
+                                       client = true;
+                               } else if (name.EndsWith("_server")) {
+                                       client = false;
+                               } else {
+                                       RunTest(testsByName[name]);
+                                       continue;
+                               }
+                               string s = name.Substring(0, name.Length - 7);
+                               RunTest(client, testsByName[s]);
+                       }
+               }
+
+               Console.WriteLine();
+               Console.WriteLine("\rtotal = {0}, failed = {1}",
+                       totalTests, totalFailures);
+       }
+
+       static void Usage()
+       {
+               Console.WriteLine(
+"usage: Twrch.exe [ options ] config [ test... ]");
+               Console.WriteLine(
+"options:");
+               Console.WriteLine(
+"   -trace    enable trace mode (hex dump of all exchanged bytes)");
+               Console.WriteLine(
+"   -cv       pass the '-v' argument to the test command");
+               Console.WriteLine(
+"   -enum     perform all version/suite/curve/hash&sign combination tests");
+               Console.WriteLine(
+"   -noenum   do NOT perform the version/suite/curve/hash&sign tests");
+               Environment.Exit(1);
+       }
+
+       static object ReadConfig(string fname)
+       {
+               using (TextReader r = File.OpenText(fname)) {
+                       return JSON.Parse(r);
+               }
+       }
+
+       int[] GetVersions()
+       {
+               string[] r = JSON.GetStringArray(conf, "versions");
+               int[] vv = new int[r.Length];
+               for (int i = 0; i < r.Length; i ++) {
+                       vv[i] = GetVersionByName(r[i]);
+               }
+               return vv;
+       }
+
+       internal static int GetVersionByName(string s)
+       {
+               s = s.Replace(" ", "").Replace(".", "").ToUpperInvariant();
+               switch (s) {
+               case "TLS10": return SSL.TLS10;
+               case "TLS11": return SSL.TLS11;
+               case "TLS12": return SSL.TLS12;
+               default:
+                       throw new Exception(string.Format(
+                               "Unknown version: '{0}'", s));
+               }
+       }
+
+       int[] GetCipherSuites()
+       {
+               return GetSuitesByName(
+                       JSON.GetStringArray(conf, "cipherSuites"));
+       }
+
+       internal static int[] GetSuitesByName(string[] ss)
+       {
+               int[] r = new int[ss.Length];
+               for (int i = 0; i < ss.Length; i ++) {
+                       r[i] = GetSuiteByName(ss[i]);
+               }
+               return r;
+       }
+
+       internal static int GetSuiteByName(string s)
+       {
+               switch (s) {
+               case "NULL_WITH_NULL_NULL":
+                       return SSL.NULL_WITH_NULL_NULL;
+               case "RSA_WITH_NULL_MD5":
+                       return SSL.RSA_WITH_NULL_MD5;
+               case "RSA_WITH_NULL_SHA":
+                       return SSL.RSA_WITH_NULL_SHA;
+               case "RSA_WITH_NULL_SHA256":
+                       return SSL.RSA_WITH_NULL_SHA256;
+               case "RSA_WITH_RC4_128_MD5":
+                       return SSL.RSA_WITH_RC4_128_MD5;
+               case "RSA_WITH_RC4_128_SHA":
+                       return SSL.RSA_WITH_RC4_128_SHA;
+               case "RSA_WITH_3DES_EDE_CBC_SHA":
+                       return SSL.RSA_WITH_3DES_EDE_CBC_SHA;
+               case "RSA_WITH_AES_128_CBC_SHA":
+                       return SSL.RSA_WITH_AES_128_CBC_SHA;
+               case "RSA_WITH_AES_256_CBC_SHA":
+                       return SSL.RSA_WITH_AES_256_CBC_SHA;
+               case "RSA_WITH_AES_128_CBC_SHA256":
+                       return SSL.RSA_WITH_AES_128_CBC_SHA256;
+               case "RSA_WITH_AES_256_CBC_SHA256":
+                       return SSL.RSA_WITH_AES_256_CBC_SHA256;
+               case "DH_DSS_WITH_3DES_EDE_CBC_SHA":
+                       return SSL.DH_DSS_WITH_3DES_EDE_CBC_SHA;
+               case "DH_RSA_WITH_3DES_EDE_CBC_SHA":
+                       return SSL.DH_RSA_WITH_3DES_EDE_CBC_SHA;
+               case "DHE_DSS_WITH_3DES_EDE_CBC_SHA":
+                       return SSL.DHE_DSS_WITH_3DES_EDE_CBC_SHA;
+               case "DHE_RSA_WITH_3DES_EDE_CBC_SHA":
+                       return SSL.DHE_RSA_WITH_3DES_EDE_CBC_SHA;
+               case "DH_DSS_WITH_AES_128_CBC_SHA":
+                       return SSL.DH_DSS_WITH_AES_128_CBC_SHA;
+               case "DH_RSA_WITH_AES_128_CBC_SHA":
+                       return SSL.DH_RSA_WITH_AES_128_CBC_SHA;
+               case "DHE_DSS_WITH_AES_128_CBC_SHA":
+                       return SSL.DHE_DSS_WITH_AES_128_CBC_SHA;
+               case "DHE_RSA_WITH_AES_128_CBC_SHA":
+                       return SSL.DHE_RSA_WITH_AES_128_CBC_SHA;
+               case "DH_DSS_WITH_AES_256_CBC_SHA":
+                       return SSL.DH_DSS_WITH_AES_256_CBC_SHA;
+               case "DH_RSA_WITH_AES_256_CBC_SHA":
+                       return SSL.DH_RSA_WITH_AES_256_CBC_SHA;
+               case "DHE_DSS_WITH_AES_256_CBC_SHA":
+                       return SSL.DHE_DSS_WITH_AES_256_CBC_SHA;
+               case "DHE_RSA_WITH_AES_256_CBC_SHA":
+                       return SSL.DHE_RSA_WITH_AES_256_CBC_SHA;
+               case "DH_DSS_WITH_AES_128_CBC_SHA256":
+                       return SSL.DH_DSS_WITH_AES_128_CBC_SHA256;
+               case "DH_RSA_WITH_AES_128_CBC_SHA256":
+                       return SSL.DH_RSA_WITH_AES_128_CBC_SHA256;
+               case "DHE_DSS_WITH_AES_128_CBC_SHA256":
+                       return SSL.DHE_DSS_WITH_AES_128_CBC_SHA256;
+               case "DHE_RSA_WITH_AES_128_CBC_SHA256":
+                       return SSL.DHE_RSA_WITH_AES_128_CBC_SHA256;
+               case "DH_DSS_WITH_AES_256_CBC_SHA256":
+                       return SSL.DH_DSS_WITH_AES_256_CBC_SHA256;
+               case "DH_RSA_WITH_AES_256_CBC_SHA256":
+                       return SSL.DH_RSA_WITH_AES_256_CBC_SHA256;
+               case "DHE_DSS_WITH_AES_256_CBC_SHA256":
+                       return SSL.DHE_DSS_WITH_AES_256_CBC_SHA256;
+               case "DHE_RSA_WITH_AES_256_CBC_SHA256":
+                       return SSL.DHE_RSA_WITH_AES_256_CBC_SHA256;
+               case "DH_anon_WITH_RC4_128_MD5":
+                       return SSL.DH_anon_WITH_RC4_128_MD5;
+               case "DH_anon_WITH_3DES_EDE_CBC_SHA":
+                       return SSL.DH_anon_WITH_3DES_EDE_CBC_SHA;
+               case "DH_anon_WITH_AES_128_CBC_SHA":
+                       return SSL.DH_anon_WITH_AES_128_CBC_SHA;
+               case "DH_anon_WITH_AES_256_CBC_SHA":
+                       return SSL.DH_anon_WITH_AES_256_CBC_SHA;
+               case "DH_anon_WITH_AES_128_CBC_SHA256":
+                       return SSL.DH_anon_WITH_AES_128_CBC_SHA256;
+               case "DH_anon_WITH_AES_256_CBC_SHA256":
+                       return SSL.DH_anon_WITH_AES_256_CBC_SHA256;
+               case "ECDH_ECDSA_WITH_NULL_SHA":
+                       return SSL.ECDH_ECDSA_WITH_NULL_SHA;
+               case "ECDH_ECDSA_WITH_RC4_128_SHA":
+                       return SSL.ECDH_ECDSA_WITH_RC4_128_SHA;
+               case "ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA":
+                       return SSL.ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA;
+               case "ECDH_ECDSA_WITH_AES_128_CBC_SHA":
+                       return SSL.ECDH_ECDSA_WITH_AES_128_CBC_SHA;
+               case "ECDH_ECDSA_WITH_AES_256_CBC_SHA":
+                       return SSL.ECDH_ECDSA_WITH_AES_256_CBC_SHA;
+               case "ECDHE_ECDSA_WITH_NULL_SHA":
+                       return SSL.ECDHE_ECDSA_WITH_NULL_SHA;
+               case "ECDHE_ECDSA_WITH_RC4_128_SHA":
+                       return SSL.ECDHE_ECDSA_WITH_RC4_128_SHA;
+               case "ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA":
+                       return SSL.ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA;
+               case "ECDHE_ECDSA_WITH_AES_128_CBC_SHA":
+                       return SSL.ECDHE_ECDSA_WITH_AES_128_CBC_SHA;
+               case "ECDHE_ECDSA_WITH_AES_256_CBC_SHA":
+                       return SSL.ECDHE_ECDSA_WITH_AES_256_CBC_SHA;
+               case "ECDH_RSA_WITH_NULL_SHA":
+                       return SSL.ECDH_RSA_WITH_NULL_SHA;
+               case "ECDH_RSA_WITH_RC4_128_SHA":
+                       return SSL.ECDH_RSA_WITH_RC4_128_SHA;
+               case "ECDH_RSA_WITH_3DES_EDE_CBC_SHA":
+                       return SSL.ECDH_RSA_WITH_3DES_EDE_CBC_SHA;
+               case "ECDH_RSA_WITH_AES_128_CBC_SHA":
+                       return SSL.ECDH_RSA_WITH_AES_128_CBC_SHA;
+               case "ECDH_RSA_WITH_AES_256_CBC_SHA":
+                       return SSL.ECDH_RSA_WITH_AES_256_CBC_SHA;
+               case "ECDHE_RSA_WITH_NULL_SHA":
+                       return SSL.ECDHE_RSA_WITH_NULL_SHA;
+               case "ECDHE_RSA_WITH_RC4_128_SHA":
+                       return SSL.ECDHE_RSA_WITH_RC4_128_SHA;
+               case "ECDHE_RSA_WITH_3DES_EDE_CBC_SHA":
+                       return SSL.ECDHE_RSA_WITH_3DES_EDE_CBC_SHA;
+               case "ECDHE_RSA_WITH_AES_128_CBC_SHA":
+                       return SSL.ECDHE_RSA_WITH_AES_128_CBC_SHA;
+               case "ECDHE_RSA_WITH_AES_256_CBC_SHA":
+                       return SSL.ECDHE_RSA_WITH_AES_256_CBC_SHA;
+               case "ECDH_anon_WITH_NULL_SHA":
+                       return SSL.ECDH_anon_WITH_NULL_SHA;
+               case "ECDH_anon_WITH_RC4_128_SHA":
+                       return SSL.ECDH_anon_WITH_RC4_128_SHA;
+               case "ECDH_anon_WITH_3DES_EDE_CBC_SHA":
+                       return SSL.ECDH_anon_WITH_3DES_EDE_CBC_SHA;
+               case "ECDH_anon_WITH_AES_128_CBC_SHA":
+                       return SSL.ECDH_anon_WITH_AES_128_CBC_SHA;
+               case "ECDH_anon_WITH_AES_256_CBC_SHA":
+                       return SSL.ECDH_anon_WITH_AES_256_CBC_SHA;
+               case "RSA_WITH_AES_128_GCM_SHA256":
+                       return SSL.RSA_WITH_AES_128_GCM_SHA256;
+               case "RSA_WITH_AES_256_GCM_SHA384":
+                       return SSL.RSA_WITH_AES_256_GCM_SHA384;
+               case "DHE_RSA_WITH_AES_128_GCM_SHA256":
+                       return SSL.DHE_RSA_WITH_AES_128_GCM_SHA256;
+               case "DHE_RSA_WITH_AES_256_GCM_SHA384":
+                       return SSL.DHE_RSA_WITH_AES_256_GCM_SHA384;
+               case "DH_RSA_WITH_AES_128_GCM_SHA256":
+                       return SSL.DH_RSA_WITH_AES_128_GCM_SHA256;
+               case "DH_RSA_WITH_AES_256_GCM_SHA384":
+                       return SSL.DH_RSA_WITH_AES_256_GCM_SHA384;
+               case "DHE_DSS_WITH_AES_128_GCM_SHA256":
+                       return SSL.DHE_DSS_WITH_AES_128_GCM_SHA256;
+               case "DHE_DSS_WITH_AES_256_GCM_SHA384":
+                       return SSL.DHE_DSS_WITH_AES_256_GCM_SHA384;
+               case "DH_DSS_WITH_AES_128_GCM_SHA256":
+                       return SSL.DH_DSS_WITH_AES_128_GCM_SHA256;
+               case "DH_DSS_WITH_AES_256_GCM_SHA384":
+                       return SSL.DH_DSS_WITH_AES_256_GCM_SHA384;
+               case "DH_anon_WITH_AES_128_GCM_SHA256":
+                       return SSL.DH_anon_WITH_AES_128_GCM_SHA256;
+               case "DH_anon_WITH_AES_256_GCM_SHA384":
+                       return SSL.DH_anon_WITH_AES_256_GCM_SHA384;
+               case "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256":
+                       return SSL.ECDHE_ECDSA_WITH_AES_128_CBC_SHA256;
+               case "ECDHE_ECDSA_WITH_AES_256_CBC_SHA384":
+                       return SSL.ECDHE_ECDSA_WITH_AES_256_CBC_SHA384;
+               case "ECDH_ECDSA_WITH_AES_128_CBC_SHA256":
+                       return SSL.ECDH_ECDSA_WITH_AES_128_CBC_SHA256;
+               case "ECDH_ECDSA_WITH_AES_256_CBC_SHA384":
+                       return SSL.ECDH_ECDSA_WITH_AES_256_CBC_SHA384;
+               case "ECDHE_RSA_WITH_AES_128_CBC_SHA256":
+                       return SSL.ECDHE_RSA_WITH_AES_128_CBC_SHA256;
+               case "ECDHE_RSA_WITH_AES_256_CBC_SHA384":
+                       return SSL.ECDHE_RSA_WITH_AES_256_CBC_SHA384;
+               case "ECDH_RSA_WITH_AES_128_CBC_SHA256":
+                       return SSL.ECDH_RSA_WITH_AES_128_CBC_SHA256;
+               case "ECDH_RSA_WITH_AES_256_CBC_SHA384":
+                       return SSL.ECDH_RSA_WITH_AES_256_CBC_SHA384;
+               case "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256":
+                       return SSL.ECDHE_ECDSA_WITH_AES_128_GCM_SHA256;
+               case "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384":
+                       return SSL.ECDHE_ECDSA_WITH_AES_256_GCM_SHA384;
+               case "ECDH_ECDSA_WITH_AES_128_GCM_SHA256":
+                       return SSL.ECDH_ECDSA_WITH_AES_128_GCM_SHA256;
+               case "ECDH_ECDSA_WITH_AES_256_GCM_SHA384":
+                       return SSL.ECDH_ECDSA_WITH_AES_256_GCM_SHA384;
+               case "ECDHE_RSA_WITH_AES_128_GCM_SHA256":
+                       return SSL.ECDHE_RSA_WITH_AES_128_GCM_SHA256;
+               case "ECDHE_RSA_WITH_AES_256_GCM_SHA384":
+                       return SSL.ECDHE_RSA_WITH_AES_256_GCM_SHA384;
+               case "ECDH_RSA_WITH_AES_128_GCM_SHA256":
+                       return SSL.ECDH_RSA_WITH_AES_128_GCM_SHA256;
+               case "ECDH_RSA_WITH_AES_256_GCM_SHA384":
+                       return SSL.ECDH_RSA_WITH_AES_256_GCM_SHA384;
+               case "ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256":
+                       return SSL.ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256;
+               case "ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256":
+                       return SSL.ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256;
+               case "DHE_RSA_WITH_CHACHA20_POLY1305_SHA256":
+                       return SSL.DHE_RSA_WITH_CHACHA20_POLY1305_SHA256;
+               case "PSK_WITH_CHACHA20_POLY1305_SHA256":
+                       return SSL.PSK_WITH_CHACHA20_POLY1305_SHA256;
+               case "ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256":
+                       return SSL.ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256;
+               case "DHE_PSK_WITH_CHACHA20_POLY1305_SHA256":
+                       return SSL.DHE_PSK_WITH_CHACHA20_POLY1305_SHA256;
+               case "RSA_PSK_WITH_CHACHA20_POLY1305_SHA256":
+                       return SSL.RSA_PSK_WITH_CHACHA20_POLY1305_SHA256;
+               default:
+                       throw new Exception(string.Format(
+                               "Unknown cipher suite: '{0}'", s));
+               }
+       }
+
+       int[] GetHashAndSigns()
+       {
+               return GetHashAndSignsByName(
+                       JSON.GetStringArray(conf, "hashAndSigns"));
+       }
+
+       internal static int[] GetHashAndSignsByName(string[] ss)
+       {
+               int[] r = new int[ss.Length];
+               for (int i = 0; i < ss.Length; i ++) {
+                       r[i] = GetHashAndSignByName(ss[i]);
+               }
+               return r;
+       }
+
+       internal static int GetHashAndSignByName(string s)
+       {
+               switch (s) {
+               case "RSA_MD5":       return SSL.RSA_MD5;
+               case "RSA_SHA1":      return SSL.RSA_SHA1;
+               case "RSA_SHA224":    return SSL.RSA_SHA224;
+               case "RSA_SHA256":    return SSL.RSA_SHA256;
+               case "RSA_SHA384":    return SSL.RSA_SHA384;
+               case "RSA_SHA512":    return SSL.RSA_SHA512;
+               case "ECDSA_MD5":     return SSL.ECDSA_MD5;
+               case "ECDSA_SHA1":    return SSL.ECDSA_SHA1;
+               case "ECDSA_SHA224":  return SSL.ECDSA_SHA224;
+               case "ECDSA_SHA256":  return SSL.ECDSA_SHA256;
+               case "ECDSA_SHA384":  return SSL.ECDSA_SHA384;
+               case "ECDSA_SHA512":  return SSL.ECDSA_SHA512;
+               default:
+                       throw new Exception(string.Format(
+                               "Unknown hash-and-sign: '{0}'", s));
+               }
+       }
+
+       int[] GetCurves()
+       {
+               return GetCurvesByName(JSON.GetStringArray(conf, "curves"));
+       }
+
+       internal static int[] GetCurvesByName(string[] ss)
+       {
+               int[] r = new int[ss.Length];
+               for (int i = 0; i < ss.Length; i ++) {
+                       r[i] = GetCurveByName(ss[i]);
+               }
+               return r;
+       }
+
+       internal static int GetCurveByName(string s)
+       {
+               switch (s) {
+               case "Curve25519":  return SSL.Curve25519;
+               case "NIST_P256":   return SSL.NIST_P256;
+               case "NIST_P384":   return SSL.NIST_P384;
+               case "NIST_P521":   return SSL.NIST_P521;
+               default:
+                       throw new Exception(string.Format(
+                               "Unknown curve: '{0}'", s));
+               }
+       }
+
+       /*
+        * RunEnum() builds and runs synthetic tests that exercise all
+        * combinations of protocol version, cipher suites, curves
+        * and hash-and-sign. Curves and hash-and-sign are enumerated
+        * only for ECDHE suites.
+        */
+       void RunEnum()
+       {
+               RunEnum(true, true);
+               RunEnum(false, true);
+       }
+
+       int RunEnum(bool cmdClient, bool doit)
+       {
+               int count = 0;
+               foreach (int version in versions) {
+                       foreach (int suite in cipherSuites) {
+                               if (version < SSL.TLS12 && SSL.IsTLS12(suite)) {
+                                       continue;
+                               }
+                               if (!SSL.IsECDHE(suite)) {
+                                       if (doit) {
+                                               RunEnum(cmdClient,
+                                                       version, suite, -1, -1);
+                                       }
+                                       count ++;
+                                       continue;
+                               }
+                               bool needRSA = (version >= SSL.TLS12)
+                                       && SSL.IsECDHE_RSA(suite);
+                               bool needECDSA = (version >= SSL.TLS12)
+                                       && SSL.IsECDHE_ECDSA(suite);
+                               foreach (int hs in hashAndSigns) {
+                                       int sa = hs & 0xFF;
+                                       if (needRSA && sa != SSL.RSA) {
+                                               continue;
+                                       }
+                                       if (needECDSA && sa != SSL.ECDSA) {
+                                               continue;
+                                       }
+                                       foreach (int curve in curves) {
+                                               if (doit) {
+                                                       RunEnum(cmdClient,
+                                                               version,
+                                                               suite,
+                                                               curve, hs);
+                                               }
+                                               count ++;
+                                       }
+                               }
+                       }
+               }
+               return count;
+       }
+
+       int ComputeTotalEnum()
+       {
+               return RunEnum(true, false) + RunEnum(false, false);
+       }
+
+       static string TEnumToString(
+               int version, int suite, int curve, int hs)
+       {
+               StringBuilder sb = new StringBuilder();
+               sb.AppendFormat("enum_{0:X4}_{1:X4}", version, suite);
+               if (curve >= 0 && hs >= 0) {
+                       sb.AppendFormat("_{0}_{1}", curve, hs);
+               }
+               return sb.ToString();
+       }
+
+       static bool StringToTEnum(string s,
+               out bool cmdClient,
+               out int version, out int suite,
+               out int curve, out int hs)
+       {
+               cmdClient = false;
+               version = -1;
+               suite = -1;
+               curve = -1;
+               hs = -1;
+               s = s.Trim();
+               if (!s.StartsWith("enum_")) {
+                       return false;
+               }
+               s = s.Substring(5);
+               if (s.EndsWith("_client")) {
+                       cmdClient = true;
+               } else if (s.EndsWith("_server")) {
+                       cmdClient = false;
+               } else {
+                       return false;
+               }
+               s = s.Substring(0, s.Length - 7);
+               string[] ww = s.Split('_');
+               if (ww.Length != 2 && ww.Length != 4) {
+                       return false;
+               }
+               version = ParseHex4(ww[0]);
+               suite = ParseHex4(ww[1]);
+               if (ww.Length == 2) {
+                       return version >= 0 && suite >= 0;
+               } else {
+                       curve = ParseDec(ww[2]);
+                       hs = ParseDec(ww[3]);
+                       return version >= 0 && suite >= 0
+                               && curve >= 0 && hs >= 0;
+               }
+       }
+
+       static int HexVal(int cp)
+       {
+               if (cp >= '0' && cp <= '9') {
+                       return cp - '0';
+               } else if (cp >= 'A' && cp <= 'F') {
+                       return cp - ('A' - 10);
+               } else if (cp >= 'a' && cp <= 'f') {
+                       return cp - ('a' - 10);
+               } else {
+                       return -1;
+               }
+       }
+
+       static int ParseHex4(string s)
+       {
+               if (s.Length != 4) {
+                       return -1;
+               }
+               return ParseHex(s);
+       }
+
+       static int ParseHex(string s)
+       {
+               int acc = 0;
+               for (int i = 0; i < 4; i ++) {
+                       int v = HexVal(s[i]);
+                       if (v < 0) {
+                               return -1;
+                       }
+                       acc = (acc << 4) + v;
+               }
+               return acc;
+       }
+
+       static int ParseDec(string s)
+       {
+               int n = s.Length;
+               int acc = 0;
+               for (int i = 0; i < n; i ++) {
+                       int v = HexVal(s[i]);
+                       if (v < 0 || v >= 10) {
+                               return -1;
+                       }
+                       acc = (acc * 10) + v;
+               }
+               return acc;
+       }
+
+       void RunEnum(bool cmdClient, int version, int suite, int curve, int hs)
+       {
+               IDictionary<string, object> d =
+                       new SortedDictionary<string, object>(
+                               StringComparer.Ordinal);
+               d["name"] = TEnumToString(version, suite, curve, hs);
+               d["versionMin"] = SSL.VersionName(version);
+               d["versionMax"] = SSL.VersionName(version);
+               d["cipherSuites"] = new string[] {
+                       SSL.CipherSuiteName(suite)
+               };
+               if (curve >= 0) {
+                       d["curves"] = new string[] {
+                               SSL.CurveName(curve)
+                       };
+                       d["hashAndSigns"] = new string[] {
+                               SSL.HashAndSignName(hs)
+                       };
+               } else {
+                       d["curves"] = new string[0];
+                       d["hashAndSigns"] = new string[0];
+               }
+               if (SSL.IsRSA(suite) || SSL.IsECDHE_RSA(suite)) {
+                       d["serverCertType"] = "RSA";
+               }
+               if (SSL.IsECDH(suite) || SSL.IsECDHE_ECDSA(suite)) {
+                       d["serverCertType"] = "EC";
+               }
+               RunTest(cmdClient, d);
+       }
+
+       /*
+        * Get certificate type for the provided test (client-side or
+        * server-side certificate, depending on 'client').
+        *
+        * If the test does not contain an explicit indication, then
+        * the certificate type will be "none" for a client, "RSA" for
+        * a server.
+        */
+       string GetCertType(object obj, bool client)
+       {
+               string name = client ? "clientCertType" : "serverCertType";
+               string ct;
+               if (JSON.TryGetString(obj, name, out ct)) {
+                       return ct;
+               }
+               return client ? "none" : "RSA";
+       }
+
+       int GetNumTests(object obj)
+       {
+               int num = 0;
+               bool v;
+               if (!JSON.TryGetBool(obj, "serverOnly", out v) || !v) {
+                       num ++;
+               }
+               if (!JSON.TryGetBool(obj, "clientOnly", out v) || !v) {
+                       num ++;
+               }
+               return num;
+       }
+
+       void RunTest(object obj)
+       {
+               bool v;
+               if (!JSON.TryGetBool(obj, "serverOnly", out v) || !v) {
+                       RunTest(true, obj);
+               }
+               if (!JSON.TryGetBool(obj, "clientOnly", out v) || !v) {
+                       RunTest(false, obj);
+               }
+       }
+
+       void RunTest(bool cmdClient, object obj)
+       {
+               string name = JSON.GetString(obj, "name")
+                       + (cmdClient ? "_client" : "_server");
+               Console.Write("\r({0}/{1})",
+                       totalSuccess + totalFailures + 1, totalTests);
+               // Console.Write("{0}:", name);
+
+               /*
+                * Expected command exit code:
+                *
+                *   0 if the command is supposed to exit gracefully
+                *   1 if the command should detect and report an error
+                */
+               int expectedExitCode;
+               JSON.TryGetInt32(obj, "expectedExitCode", out expectedExitCode);
+
+               /*
+                * Expected failure: if defined, then we expect our
+                * library to throw an exception, and the message should
+                * contain that specific string.
+                */
+               string expectedFailure;
+               JSON.TryGetString(obj, "expectedFailure", out expectedFailure);
+
+               /*
+                * Assemble the sub-process command line:
+                *
+                *  - Always one of "-client" or "-server"
+                *  - For a server command, a certificate and key are
+                *    always provided (defaults to RSA); for a client,
+                *    only if explicitly asked for.
+                */
+               StringBuilder sb = new StringBuilder();
+               if (cmdClient) {
+                       sb.Append("-client");
+               } else {
+                       sb.Append("-server");
+               }
+               if (commandVerbose) {
+                       sb.Append(" -v");
+               }
+               string certType = GetCertType(obj, cmdClient);
+               switch (certType) {
+               case "RSA":
+                       sb.AppendFormat(" -cert \"{0}\" -key \"{1}\"",
+                               chainRSAFile, skeyRSAFile);
+                       break;
+               case "EC":
+                       sb.AppendFormat(" -cert \"{0}\" -key \"{1}\"",
+                               chainECFile, skeyECFile);
+                       break;
+               case "none":
+                       break;
+               default:
+                       throw new Exception("Unknown certType: " + certType);
+               }
+               string extra;
+               if (JSON.TryGetString(obj, "extraArgs", out extra)) {
+                       sb.Append(' ');
+                       sb.Append(extra);
+               }
+
+               /*
+                * Run the sub-process.
+                */
+               ProcessStartInfo si = new ProcessStartInfo();
+               si.FileName = commandFile;
+               si.Arguments = string.Format(commandArgs, sb.ToString());
+               si.UseShellExecute = false;
+               si.ErrorDialog = false;
+               si.CreateNoWindow = true;
+               si.RedirectStandardInput = true;
+               si.RedirectStandardOutput = true;
+
+               using (Process pp = new Process()) {
+                       pp.StartInfo = si;
+                       pp.Start();
+                       Exception delayed = null;
+                       try {
+                               /*
+                                * TODO: add a time-out on the streams
+                                * so that the test never stalls
+                                * indefinitely if the two SSL engines
+                                * lose synchronisation.
+                                */
+                               MergeStream ms = new MergeStream(
+                                       pp.StandardOutput.BaseStream,
+                                       pp.StandardInput.BaseStream);
+                               if (trace) {
+                                       ms.Debug = Console.Out;
+                               }
+                               RunTestInner(cmdClient, obj, ms);
+                       } catch (Exception ex) {
+                               delayed = ex;
+                       }
+
+                       /*
+                        * Once the test has run, we must make sure that
+                        * the sub-processed is finished. It _should_ end
+                        * properly by itself for all successful test cases,
+                        * so if we have to kill it, then it's a bug.
+                        */
+                       bool killed = false;
+                       if (!pp.WaitForExit(2000)) {
+                               try {
+                                       pp.Kill();
+                               } catch {
+                                       // ignored
+                               }
+                               pp.WaitForExit();
+                               killed = true;
+                       }
+                       int exc = pp.ExitCode;
+
+                       /*
+                        * If we had to kill the command, then that is
+                        * always a bug. Otherwise, we compare what we
+                        * got with the expected outcomes.
+                        */
+                       List<string> msg = new List<string>();
+                       if (killed) {
+                               msg.Add("COMMAND KILLED");
+                       }
+                       if (exc != expectedExitCode) {
+                               msg.Add("Wrong exit code: "
+                                       + exc + " (expected: "
+                                       + expectedExitCode + ")");
+                       }
+                       if (delayed == null) {
+                               if (expectedFailure != null) {
+                                       msg.Add("An exception was expected");
+                               }
+                       } else {
+                               if (expectedFailure == null) {
+                                       msg.Add(delayed.ToString());
+                               } else {
+                                       string s = delayed.Message;
+                                       if (s == null) {
+                                               s = "";
+                                       }
+                                       if (s.IndexOf(expectedFailure) < 0) {
+                                               msg.Add(delayed.ToString());
+                                       }
+                               }
+                       }
+                       if (msg.Count == 0) {
+                               totalSuccess ++;
+                       } else {
+                               Console.WriteLine("{0}: FAIL:", name);
+                               foreach (string s in msg) {
+                                       Console.WriteLine(s);
+                               }
+                               totalFailures ++;
+                       }
+               }
+       }
+
+       void RunTestInner(bool cmdClient, object obj, Stream peer)
+       {
+               /*
+                * Create the SSL engine, and configure it as specified
+                * in the configuration object (with the default
+                * configuration as fallback).
+                */
+
+               SSLEngine eng;
+               byte[][] chain = null;
+               IPrivateKey skey = null;
+               string certType = GetCertType(obj, !cmdClient);
+               switch (certType) {
+               case "RSA":
+                       chain = chainRSA;
+                       skey = skeyRSA;
+                       break;
+               case "EC":
+                       chain = chainEC;
+                       skey = skeyEC;
+                       break;
+               case "none":
+                       break;
+               default:
+                       throw new Exception("Unknown certType: " + certType);
+               }
+               if (cmdClient) {
+                       IServerPolicy spol = new SSLServerPolicyBasic(
+                               chain, skey, KeyUsage.EncryptAndSign);
+                       SSLServer ss = new SSLServer(peer, spol);
+                       ss.SessionCache = new SSLSessionCacheLRU(20);
+                       eng = ss;
+               } else {
+                       SSLClient sc = new SSLClient(peer);
+                       sc.ServerCertValidator =
+                               SSLClient.InsecureCertValidator;
+                       eng = sc;
+               }
+               eng.NormalizeIOError = true;
+               eng.AutoFlush = false;
+
+               /*
+                * Minimum version.
+                */
+               string svmin;
+               if (JSON.TryGetString(obj, "versionMin", out svmin)) {
+                       eng.VersionMin = GetVersionByName(svmin);
+               } else {
+                       eng.VersionMin = versionMin;
+               }
+
+               /*
+                * Maximum version.
+                */
+               string svmax;
+               if (JSON.TryGetString(obj, "versionMax", out svmax)) {
+                       eng.VersionMax = GetVersionByName(svmax);
+               } else {
+                       eng.VersionMax = versionMax;
+               }
+
+               /*
+                * Supported cipher suites.
+                */
+               string[] sccs;
+               if (JSON.TryGetStringArray(obj, "cipherSuites", out sccs)) {
+                       eng.SupportedCipherSuites = GetSuitesByName(sccs);
+               } else {
+                       eng.SupportedCipherSuites = cipherSuites;
+               }
+
+               /*
+                * Supported hash-and-sign algorithms.
+                */
+               string[] shss;
+               if (JSON.TryGetStringArray(obj, "hashAndSigns", out shss)) {
+                       eng.SupportedHashAndSign = GetHashAndSignsByName(shss);
+               } else {
+                       eng.SupportedHashAndSign = hashAndSigns;
+               }
+
+               /*
+                * Supported elliptic curves.
+                */
+               string[] secc;
+               if (JSON.TryGetStringArray(obj, "curves", out secc)) {
+                       eng.SupportedCurves = GetCurvesByName(secc);
+               } else {
+                       eng.SupportedCurves = curves;
+               }
+
+               /*
+                * What to do when there is no close_notify.
+                */
+               bool ncn;
+               if (JSON.TryGetBool(obj, "noCloseNotify", out ncn)) {
+                       eng.NoCloseNotify = ncn;
+               } else {
+                       eng.NoCloseNotify = noCloseNotify;
+               }
+
+               /*
+                * Quirks.
+                */
+               IDictionary<string, object> qm;
+               if (JSON.TryGetObjectMap(obj, "quirks", out qm)) {
+                       SSLQuirks q = new SSLQuirks();
+                       foreach (string name in qm.Keys) {
+                               q[name] = JSON.GetString(qm, name);
+                       }
+                       eng.Quirks = q;
+               }
+
+               bool askClose;
+               JSON.TryGetBool(obj, "askClose", out askClose);
+               bool renegotiate, renegotiateAccepted;
+               renegotiate = JSON.TryGetBool(obj, "renegotiate",
+                       out renegotiateAccepted);
+               bool askRenegotiate, askRenegotiateAccepted;
+               askRenegotiate = JSON.TryGetBool(obj, "askRenegotiate",
+                       out askRenegotiateAccepted);
+
+               bool reconnectSelf = false, reconnectPeer = false;
+               string rcs;
+               if (JSON.TryGetString(obj, "reconnect", out rcs)) {
+                       switch (rcs) {
+                       case "self": reconnectSelf = true; break;
+                       case "peer": reconnectPeer = true; break;
+                       default:
+                               throw new Exception("Unknown 'reconnect' type: "
+                                       + rcs);
+                       }
+               }
+
+               bool forgetSelf = false, forgetPeer = false;
+               string fgs;
+               if (JSON.TryGetString(obj, "forget", out fgs)) {
+                       switch (fgs) {
+                       case "self": forgetSelf = true; break;
+                       case "peer": forgetPeer = true; break;
+                       default:
+                               throw new Exception("Unknown 'forget' type: "
+                                       + fgs);
+                       }
+               }
+
+               if (askClose) {
+                       SendCommand(eng, 'C');
+                       if (eng.ReadByte() != -1) {
+                               throw new Exception("Peer did not close");
+                       }
+               } else if (renegotiate) {
+                       SendMessageNormal(eng, 10);
+                       if (eng.Renegotiate()) {
+                               if (!renegotiateAccepted) {
+                                       throw new Exception("Renegotiation"
+                                               + " should have been rejected");
+                               }
+                       } else {
+                               if (renegotiateAccepted) {
+                                       throw new Exception("Renegotiation"
+                                               + " should have been accepted");
+                               }
+                       }
+                       SendMessageNormal(eng, 9);
+               } else if (askRenegotiate) {
+                       SendMessageNormal(eng, 10);
+                       long rc = eng.HandshakeCount;
+                       SendCommand(eng, 'G');
+                       string s = ReadLine(eng);
+                       switch (s) {
+                       case "DENIED":
+                               if (askRenegotiateAccepted) {
+                                       throw new Exception("Renegotiation"
+                                               + " should have been accepted");
+                               }
+                               break;
+                       case "OK":
+                               if (!askRenegotiateAccepted) {
+                                       throw new Exception("Renegotiation"
+                                               + " should have been rejected");
+                               }
+                               long nrc = eng.HandshakeCount;
+                               if (nrc != rc + 1) {
+                                       throw new Exception(string.Format(
+                                               "Wrong handshake count"
+                                               + " (old={0}, new={1})",
+                                               rc, nrc));
+                               }
+                               break;
+                       default:
+                               throw new Exception(string.Format(
+                                       "Unexpected answer string '{0}'", s));
+                       }
+                       SendMessageNormal(eng, 8);
+               } else if (reconnectSelf || reconnectPeer) {
+                       SendMessageNormal(eng, 50);
+                       SendMessageNormal(eng, 100);
+                       if (forgetPeer) {
+                               SendCommand(eng, 'U');
+                               string s = ReadLine(eng);
+                               if (s != "DONE") {
+                                       throw new Exception(string.Format(
+                                               "Unexpected answer '{0}'", s));
+                               }
+                       }
+                       eng.CloseSub = false;
+                       if (reconnectPeer) {
+                               SendCommand(eng, 'T');
+                               if (eng.ReadByte() != -1) {
+                                       throw new Exception(
+                                               "Peer did not close");
+                               }
+                       } else {
+                               SendCommand(eng, 'R');
+                               string s = ReadLine(eng);
+                               if (s != "OK") {
+                                       throw new Exception(string.Format(
+                                               "Unexpected answer '{0}'", s));
+                               }
+                               eng.Close();
+                       }
+                       SSLEngine eng2;
+                       if (cmdClient) {
+                               IServerPolicy spol = new SSLServerPolicyBasic(
+                                       chain, skey, KeyUsage.EncryptAndSign);
+                               SSLServer ss = new SSLServer(peer, spol);
+                               if (forgetSelf) {
+                                       ss.SessionCache =
+                                               new SSLSessionCacheLRU(20);
+                               } else {
+                                       ss.SessionCache =
+                                               ((SSLServer)eng).SessionCache;
+                               }
+                               eng2 = ss;
+                       } else {
+                               SSLSessionParameters sp;
+                               if (forgetSelf) {
+                                       sp = null;
+                               } else {
+                                       sp = eng.SessionParameters;
+                               }
+                               SSLClient sc = new SSLClient(peer, sp);
+                               sc.ServerCertValidator =
+                                       SSLClient.InsecureCertValidator;
+                               eng2 = sc;
+                       }
+                       eng2.NormalizeIOError = eng.NormalizeIOError;
+                       eng2.AutoFlush = eng.AutoFlush;
+                       eng2.VersionMin = eng.VersionMin;
+                       eng2.VersionMax = eng.VersionMax;
+                       eng2.SupportedCipherSuites = eng.SupportedCipherSuites;
+                       eng2.SupportedHashAndSign = eng.SupportedHashAndSign;
+                       eng2.SupportedCurves = eng.SupportedCurves;
+                       eng2.NoCloseNotify = eng.NoCloseNotify;
+                       eng2.Quirks = eng.Quirks;
+                       eng = eng2;
+                       SendMessageNormal(eng, 60);
+                       SendMessageNormal(eng, 90);
+                       if (forgetSelf || forgetPeer) {
+                               if (eng.IsResume) {
+                                       throw new Exception(
+                                               "Session was resumed");
+                               }
+                       } else {
+                               if (!eng.IsResume) {
+                                       throw new Exception(
+                                               "Session was not resumed");
+                               }
+                       }
+               } else {
+                       for (int i = 0; i <= 38; i ++) {
+                               int len;
+                               if (i <= 20) {
+                                       len = i;
+                               } else {
+                                       len = 20 + (1 << (i - 20));
+                               }
+                               SendMessageNormal(eng, len);
+                       }
+               }
+
+               eng.Close();
+       }
+
+       /*
+        * Send a "normal" message to the peer, of the specified
+        * length: this is a sequence of 'len' random bytes, distinct
+        * from 0x0A, followed one 0x0A byte. The peer is supposed to
+        * respond with the SHA-1 hash of the message bytes (excluding
+        * the final 0x0A), encoded in hexadecimal (lowercase) and
+        * followed by a newline (0x0A). An exception is thrown if the
+        * expected value is not obtained.
+        */
+       void SendMessageNormal(SSLEngine eng, int len)
+       {
+               SHA1 sha1 = new SHA1();
+               byte[] buf = new byte[len + 1];
+               RNG.GetBytesNonZero(buf, 0, len);
+               for (int i = 0; i < len; i ++) {
+                       buf[i] ^= 0x0A;
+               }
+               buf[len] = 0x0A;
+               if (len == 1) {
+                       buf[0] = (byte)('a' + (buf[0] & 0x0F));
+               }
+               StringBuilder sb = new StringBuilder();
+               foreach (byte b in sha1.Hash(buf, 0, len)) {
+                       sb.AppendFormat("{0:x2}", b);
+               }
+               sb.Append('\n');
+               eng.Write(buf, 0, buf.Length);
+               eng.Flush();
+               for (int i = 0; i < sb.Length; i ++) {
+                       int x = eng.ReadByte();
+                       int y = sb[i];
+                       if (x != y) {
+                               throw new Exception(string.Format(
+                                       "received {0} (exp: {1})", y, x));
+                       }
+               }
+       }
+
+       void SendCommand(SSLEngine eng, char cmd)
+       {
+               eng.WriteByte((byte)cmd);
+               eng.WriteByte(0x0A);
+               eng.Flush();
+       }
+
+       string ReadLine(SSLEngine eng)
+       {
+               StringBuilder sb = new StringBuilder();
+               for (;;) {
+                       int c = eng.ReadByte();
+                       if (c < 0) {
+                               throw new Exception("Unexpected EOF");
+                       }
+                       if (c == 0x0A) {
+                               return sb.ToString();
+                       }
+                       sb.Append((char)c);
+               }
+       }
+
+       static byte[][] DecodeChain(string fname)
+       {
+               byte[] buf = File.ReadAllBytes(fname);
+               PEMObject[] fpo = AsnIO.DecodePEM(buf);
+               if (fpo.Length == 0) {
+                       buf = AsnIO.FindBER(buf);
+                       if (buf == null) {
+                               throw new Exception(string.Format(
+                                       "No certificate in file '{0}'", fname));
+                       }
+                       return new byte[][] { buf };
+               }
+               List<byte[]> r = new List<byte[]>();
+               foreach (PEMObject po in fpo) {
+                       string tt = po.type.ToUpperInvariant();
+                       if (tt == "CERTIFICATE" || tt == "X509 CERTIFICATE") {
+                               r.Add(po.data);
+                       }
+               }
+               if (r.Count == 0) {
+                       throw new Exception(string.Format(
+                               "No certificate in file '{0}'", fname));
+               }
+               return r.ToArray();
+       }
+
+       static IPrivateKey DecodePrivateKey(string fname)
+       {
+               byte[] buf = File.ReadAllBytes(fname);
+               PEMObject[] fpo = AsnIO.DecodePEM(buf);
+               if (fpo.Length == 0) {
+                       buf = AsnIO.FindBER(buf);
+               } else {
+                       buf = null;
+                       foreach (PEMObject po in fpo) {
+                               string tt = po.type.ToUpperInvariant();
+                               if (tt.IndexOf("PRIVATE KEY") >= 0) {
+                                       if (buf != null) {
+                                               throw new Exception(
+                                                       "Multiple keys in '"
+                                                       + fname + "'");
+                                       }
+                                       buf = po.data;
+                               }
+                       }
+               }
+               if (buf == null) {
+                       throw new Exception(string.Format(
+                               "No private key in file '{0}'", fname));
+               }
+               return KF.DecodePrivateKey(buf);
+       }
+}
diff --git a/X500/DNPart.cs b/X500/DNPart.cs
new file mode 100644 (file)
index 0000000..57f02a3
--- /dev/null
@@ -0,0 +1,550 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+using Asn1;
+
+namespace X500 {
+
+/*
+ * A DNPart instance encodes an X.500 name element: it has a type (an
+ * OID) and a value. The value is an ASN.1 object. If the name type is
+ * one of a list of standard types, then the value is a character
+ * string, and there is a "friendly type" which is a character string
+ * (such as "CN" for the "common name", of OID 2.5.4.3).
+ */
+
+public class DNPart {
+
+       /*
+        * These are the known "friendly types". The values decode as
+        * strings.
+        */
+
+       public const string COMMON_NAME          = "CN";
+       public const string LOCALITY             = "L";
+       public const string STATE                = "ST";
+       public const string ORGANIZATION         = "O";
+       public const string ORGANIZATIONAL_UNIT  = "OU";
+       public const string COUNTRY              = "C";
+       public const string STREET               = "STREET";
+       public const string DOMAIN_COMPONENT     = "DC";
+       public const string USER_ID              = "UID";
+       public const string EMAIL_ADDRESS        = "EMAILADDRESS";
+
+       /*
+        * Get the type OID (decimal-dotted string representation).
+        */
+       public string OID {
+               get {
+                       return OID_;
+               }
+       }
+       string OID_;
+
+       /*
+        * Get the string value for this element. If the element value
+        * could not be decoded as a string, then this method returns
+        * null.
+        *
+        * (Decoding error for name elements of a standard type trigger
+        * exceptions upon instance creation. Thus, a null value is
+        * possible only for a name element that uses an unknown type.)
+        */
+       public string Value {
+               get {
+                       return Value_;
+               }
+       }
+       string Value_;
+
+       /*
+        * Tell whether this element is string based. This property
+        * returns true if and only if Value returns a non-null value.
+        */
+       public bool IsString {
+               get {
+                       return Value_ != null;
+               }
+       }
+
+       /*
+        * Get the element value as an ASN.1 structure.
+        */
+       public AsnElt AsnValue {
+               get {
+                       return AsnValue_;
+               }
+       }
+       AsnElt AsnValue_;
+
+       /*
+        * Get the "friendly type" for this element. This is the
+        * string mnemonic such as "CN" for "common name". If no
+        * friendly type is known for that element, then the OID
+        * is returned (decimal-dotted representation).
+        */
+       public string FriendlyType {
+               get {
+                       return GetFriendlyType(OID);
+               }
+       }
+
+       /*
+        * "Normalized" string value (converted to uppercase then
+        * lowercase, leading and trailing whitespace trimmed, adjacent
+        * spaces coalesced). This should allow for efficient comparison
+        * while still supporting most corner cases.
+        *
+        * This does not implement full RFC 4518 rules, but it should
+        * be good enough for an analysis tool.
+        */
+       string normValue;
+
+       byte[] encodedValue;
+       int hashCode;
+
+       internal DNPart(string oid, AsnElt val)
+       {
+               OID_ = oid;
+               AsnValue_ = val;
+               encodedValue = val.Encode();
+               uint hc = (uint)oid.GetHashCode();
+               try {
+                       string s = val.GetString();
+                       Value_ = s;
+                       s = s.ToUpperInvariant().ToLowerInvariant();
+                       StringBuilder sb = new StringBuilder();
+                       bool lwws = true;
+                       foreach (char c in s.Trim()) {
+                               if (IsControl(c)) {
+                                       continue;
+                               }
+                               if (IsWS(c)) {
+                                       if (lwws) {
+                                               continue;
+                                       }
+                                       lwws = true;
+                                       sb.Append(' ');
+                               } else {
+                                       sb.Append(c);
+                               }
+                       }
+                       int n = sb.Length;
+                       if (n > 0 && sb[n - 1] == ' ') {
+                               sb.Length = n - 1;
+                       }
+                       normValue = sb.ToString();
+                       hc += (uint)normValue.GetHashCode();
+               } catch {
+                       if (OID_TO_FT.ContainsKey(oid)) {
+                               throw;
+                       }
+                       Value_ = null;
+                       foreach (byte b in encodedValue) {
+                               hc = ((hc << 7) | (hc >> 25)) ^ (uint)b;
+                       }
+               }
+               hashCode = (int)hc;
+       }
+
+       static bool MustEscape(int x)
+       {
+               if (x < 0x20 || x >= 0x7F) {
+                       return true;
+               }
+               switch (x) {
+               case '"':
+               case '+':
+               case ',':
+               case ';':
+               case '<':
+               case '>':
+               case '\\':
+                       return true;
+               default:
+                       return false;
+               }
+       }
+
+       /*
+        * Convert this element to a string. This uses RFC 4514 rules.
+        */
+       public override string ToString()
+       {
+               StringBuilder sb = new StringBuilder();
+               string ft;
+               if (OID_TO_FT.TryGetValue(OID, out ft) && IsString) {
+                       sb.Append(ft);
+                       sb.Append("=");
+                       byte[] buf = Encoding.UTF8.GetBytes(Value);
+                       for (int i = 0; i < buf.Length; i ++) {
+                               byte b = buf[i];
+                               if ((i == 0 && (b == ' ' || b == '#'))
+                                       || (i == buf.Length - 1 && b == ' ')
+                                       || MustEscape(b))
+                               {
+                                       switch ((char)b) {
+                                       case ' ':
+                                       case '"':
+                                       case '#':
+                                       case '+':
+                                       case ',':
+                                       case ';':
+                                       case '<':
+                                       case '=':
+                                       case '>':
+                                       case '\\':
+                                               sb.Append('\\');
+                                               sb.Append((char)b);
+                                               break;
+                                       default:
+                                               sb.AppendFormat("\\{0:X2}", b);
+                                               break;
+                                       }
+                               } else {
+                                       sb.Append((char)b);
+                               }
+                       }
+               } else {
+                       sb.Append(OID);
+                       sb.Append("=#");
+                       foreach (byte b in AsnValue.Encode()) {
+                               sb.AppendFormat("{0:X2}", b);
+                       }
+               }
+               return sb.ToString();
+       }
+
+       /*
+        * Get the friendly type corresponding to the given OID
+        * (decimal-dotted representation). If no such type is known,
+        * then the OID string is returned.
+        */
+       public static string GetFriendlyType(string oid)
+       {
+               string ft;
+               if (OID_TO_FT.TryGetValue(oid, out ft)) {
+                       return ft;
+               }
+               return oid;
+       }
+
+       static int HexVal(char c)
+       {
+               if (c >= '0' && c <= '9') {
+                       return c - '0';
+               } else if (c >= 'A' && c <= 'F') {
+                       return c - ('A' - 10);
+               } else if (c >= 'a' && c <= 'f') {
+                       return c - ('a' - 10);
+               } else {
+                       return -1;
+               }
+       }
+
+       static int HexValCheck(char c)
+       {
+               int x = HexVal(c);
+               if (x < 0) {
+                       throw new AsnException(String.Format(
+                               "Not an hex digit: U+{0:X4}", c));
+               }
+               return x;
+       }
+
+       static int HexVal2(string str, int k)
+       {
+               if (k >= str.Length) {
+                       throw new AsnException("Missing hex digits");
+               }
+               int x = HexVal(str[k]);
+               if ((k + 1) >= str.Length) {
+                       throw new AsnException("Odd number of hex digits");
+               }
+               return (x << 4) + HexVal(str[k + 1]);
+       }
+
+       static int ReadHexEscape(string str, ref int off)
+       {
+               if (off >= str.Length || str[off] != '\\') {
+                       return -1;
+               }
+               if ((off + 1) >= str.Length) {
+                       throw new AsnException("Truncated escape");
+               }
+               int x = HexVal(str[off + 1]);
+               if (x < 0) {
+                       return -1;
+               }
+               if ((off + 2) >= str.Length) {
+                       throw new AsnException("Truncated escape");
+               }
+               int y = HexValCheck(str[off + 2]);
+               off += 3;
+               return (x << 4) + y;
+       }
+
+       static int ReadHexUTF(string str, ref int off)
+       {
+               int x = ReadHexEscape(str, ref off);
+               if (x < 0x80 || x >= 0xC0) {
+                       throw new AsnException(
+                               "Invalid hex escape: not UTF-8");
+               }
+               return x;
+       }
+
+       static string UnEscapeUTF8(string str)
+       {
+               StringBuilder sb = new StringBuilder();
+               int n = str.Length;
+               int k = 0;
+               while (k < n) {
+                       char c = str[k];
+                       if (c != '\\') {
+                               sb.Append(c);
+                               k ++;
+                               continue;
+                       }
+                       int x = ReadHexEscape(str, ref k);
+                       if (x < 0) {
+                               sb.Append(str[k + 1]);
+                               k += 2;
+                               continue;
+                       }
+                       if (x < 0x80) {
+                               // nothing
+                       } else if (x < 0xC0) {
+                               throw new AsnException(
+                                       "Invalid hex escape: not UTF-8");
+                       } else if (x < 0xE0) {
+                               x &= 0x1F;
+                               x = (x << 6) | ReadHexUTF(str, ref k) & 0x3F;
+                       } else if (x < 0xF0) {
+                               x &= 0x0F;
+                               x = (x << 6) | ReadHexUTF(str, ref k) & 0x3F;
+                               x = (x << 6) | ReadHexUTF(str, ref k) & 0x3F;
+                       } else if (x < 0xF8) {
+                               x &= 0x07;
+                               x = (x << 6) | ReadHexUTF(str, ref k) & 0x3F;
+                               x = (x << 6) | ReadHexUTF(str, ref k) & 0x3F;
+                               x = (x << 6) | ReadHexUTF(str, ref k) & 0x3F;
+                               if (x > 0x10FFFF) {
+                                       throw new AsnException("Invalid"
+                                               + " hex escape: out of range");
+                               }
+                       } else {
+                               throw new AsnException(
+                                       "Invalid hex escape: not UTF-8");
+                       }
+                       if (x < 0x10000) {
+                               sb.Append((char)x);
+                       } else {
+                               x -= 0x10000;
+                               sb.Append((char)(0xD800 + (x >> 10)));
+                               sb.Append((char)(0xDC00 + (x & 0x3FF)));
+                       }
+               }
+               return sb.ToString();
+       }
+
+       internal static DNPart Parse(string str)
+       {
+               int j = str.IndexOf('=');
+               if (j < 0) {
+                       throw new AsnException("Invalid DN: no '=' sign");
+               }
+               string a = str.Substring(0, j).Trim();
+               string b = str.Substring(j + 1).Trim();
+               string oid;
+               if (!FT_TO_OID.TryGetValue(a, out oid)) {
+                       oid = AsnElt.MakeOID(oid).GetOID();
+               }
+               AsnElt aVal;
+               if (b.StartsWith("#")) {
+                       MemoryStream ms = new MemoryStream();
+                       int n = b.Length;
+                       for (int k = 1; k < n; k += 2) {
+                               int x = HexValCheck(b[k]);
+                               if (k + 1 >= n) {
+                                       throw new AsnException(
+                                               "Odd number of hex digits");
+                               }
+                               x = (x << 4) + HexValCheck(b[k + 1]);
+                               ms.WriteByte((byte)x);
+                       }
+                       try {
+                               aVal = AsnElt.Decode(ms.ToArray());
+                       } catch (Exception e) {
+                               throw new AsnException("Bad DN value: "
+                                       + e.Message);
+                       }
+               } else {
+                       b = UnEscapeUTF8(b);
+                       int type = AsnElt.PrintableString;
+                       foreach (char c in b) {
+                               if (!AsnElt.IsPrintable(c)) {
+                                       type = AsnElt.UTF8String;
+                                       break;
+                               }
+                       }
+                       aVal = AsnElt.MakeString(type, b);
+               }
+               return new DNPart(oid, aVal);
+       }
+
+       static Dictionary<string, string> OID_TO_FT;
+       static Dictionary<string, string> FT_TO_OID;
+
+       static void AddFT(string oid, string ft)
+       {
+               OID_TO_FT[oid] = ft;
+               FT_TO_OID[ft] = oid;
+       }
+
+       static DNPart()
+       {
+               OID_TO_FT = new Dictionary<string, string>();
+               FT_TO_OID = new Dictionary<string, string>(
+                       StringComparer.OrdinalIgnoreCase);
+               AddFT("2.5.4.3", COMMON_NAME);
+               AddFT("2.5.4.7", LOCALITY);
+               AddFT("2.5.4.8", STATE);
+               AddFT("2.5.4.10", ORGANIZATION);
+               AddFT("2.5.4.11", ORGANIZATIONAL_UNIT);
+               AddFT("2.5.4.6", COUNTRY);
+               AddFT("2.5.4.9", STREET);
+               AddFT("0.9.2342.19200300.100.1.25", DOMAIN_COMPONENT);
+               AddFT("0.9.2342.19200300.100.1.1", USER_ID);
+               AddFT("1.2.840.113549.1.9.1", EMAIL_ADDRESS);
+
+               /*
+                * We also accept 'S' as an alias for 'ST' because some
+                * Microsoft software uses it.
+                */
+               FT_TO_OID["S"] = FT_TO_OID["ST"];
+       }
+
+       /*
+        * Tell whether a given character is a "control character" (to
+        * be ignored for DN comparison purposes). This follows RFC 4518
+        * but only for code points in the first plane.
+        */
+       static bool IsControl(char c)
+       {
+               if (c <= 0x0008
+                       || (c >= 0x000E && c <= 0x001F)
+                       || (c >= 0x007F && c <= 0x0084)
+                       || (c >= 0x0086 && c <= 0x009F)
+                       || c == 0x06DD
+                       || c == 0x070F
+                       || c == 0x180E
+                       || (c >= 0x200C && c <= 0x200F)
+                       || (c >= 0x202A && c <= 0x202E)
+                       || (c >= 0x2060 && c <= 0x2063)
+                       || (c >= 0x206A && c <= 0x206F)
+                       || c == 0xFEFF
+                       || (c >= 0xFFF9 && c <= 0xFFFB))
+               {
+                       return true;
+               }
+               return false;
+       }
+
+       /*
+        * Tell whether a character is whitespace. This follows
+        * rules of RFC 4518.
+        */
+       static bool IsWS(char c)
+       {
+               if (c == 0x0020
+                       || c == 0x00A0
+                       || c == 0x1680
+                       || (c >= 0x2000 && c <= 0x200A)
+                       || c == 0x2028
+                       || c == 0x2029
+                       || c == 0x202F
+                       || c == 0x205F
+                       || c == 0x3000)
+               {
+                       return true;
+               }
+               return false;
+       }
+
+       public override bool Equals(object obj)
+       {
+               return Equals(obj as DNPart);
+       }
+
+       public bool Equals(DNPart dnp)
+       {
+               if (dnp == null) {
+                       return false;
+               }
+               if (OID != dnp.OID) {
+                       return false;
+               }
+               if (IsString) {
+                       return dnp.IsString
+                               && normValue == dnp.normValue;
+               } else if (dnp.IsString) {
+                       return false;
+               } else {
+                       return Eq(encodedValue, dnp.encodedValue);
+               }
+       }
+
+       public override int GetHashCode()
+       {
+               return hashCode;
+       }
+
+       static bool Eq(byte[] a, byte[] b)
+       {
+               if (a == b) {
+                       return true;
+               }
+               if (a == null || b == null) {
+                       return false;
+               }
+               int n = a.Length;
+               if (n != b.Length) {
+                       return false;
+               }
+               for (int i = 0; i < n; i ++) {
+                       if (a[i] != b[i]) {
+                               return false;
+                       }
+               }
+               return true;
+       }
+}
+
+}
diff --git a/X500/X500Name.cs b/X500/X500Name.cs
new file mode 100644 (file)
index 0000000..438faa6
--- /dev/null
@@ -0,0 +1,343 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using Asn1;
+
+namespace X500 {
+
+/*
+ * An X.500 name is a "distinguished name", which is an ordered sequence
+ * of RDN (relative distinguished names). Each RDN is an unordered set
+ * of name elements. A name element is an arbitrary value with an
+ * identifying OID; some (most) name element values are character
+ * strings.
+ *
+ * X.500 names are used primarily to identify certificate owner entities
+ * (subject and issuer name in a certificate), and to serve as
+ * hierarchical indexing key for values in LDAP.
+ *
+ * An X.500 name is encoded and decoded using ASN.1. They can also be
+ * converted to a string representation. The string representation does
+ * not conserve all encoding details, so encoding+parsing will not
+ * necessarily restore the exact same binary DN.
+ */
+
+public class X500Name {
+
+       /*
+        * Get the individual name elements, in a "flattened" structure
+        * (if a SET in a RDN contains multiple values, then they are
+        * stored consecutively in that array).
+        *
+        * The returned array MUST NOT be modified.
+        */
+       public DNPart[] Parts {
+               get {
+                       return Parts_;
+               }
+       }
+       DNPart[] Parts_;
+
+       /*
+        * Get the individual name elements. Each internal array contains
+        * the name elements found in the SET for a specific RDN.
+        */
+       public DNPart[][] PartsGen {
+               get {
+                       return PartsGen_;
+               }
+       }
+       DNPart[][] PartsGen_;
+
+       /*
+        * Check whether this DN is empty.
+        */
+       public bool IsEmpty {
+               get {
+                       return Parts_.Length == 0;
+               }
+       }
+
+       int hashCode;
+
+       /*
+        * Constructor for parsing.
+        */
+       X500Name(List<List<DNPart>> dn)
+       {
+               Init(dn);
+       }
+
+       void Init(List<List<DNPart>> dn)
+       {
+               int n = dn.Count;
+               List<DNPart> r = new List<DNPart>();
+               PartsGen_ = new DNPart[n][];
+               for (int i = 0; i < n; i ++) {
+                       IDictionary<string, DNPart> dd =
+                               new SortedDictionary<string, DNPart>(
+                                       StringComparer.Ordinal);
+                       foreach (DNPart dnp in dn[i]) {
+                               string nt = dnp.OID;
+                               if (dd.ContainsKey(nt)) {
+                                       throw new AsnException(string.Format(
+                                               "multiple values of type {0}"
+                                               + " in RDN", nt));
+                               }
+                               dd[nt] = dnp;
+                       }
+                       PartsGen_[i] = new DNPart[dd.Count];
+                       int j = 0;
+                       foreach (DNPart p in dd.Values) {
+                               PartsGen_[i][j ++] = p;
+                               r.Add(p);
+                       }
+               }
+               Parts_ = r.ToArray();
+
+               uint hc = 0;
+               foreach (DNPart dnp in r) {
+                       hc = ((hc << 7) | (hc >> 25)) + (uint)dnp.GetHashCode();
+               }
+               hashCode = (int)hc;
+       }
+
+       /*
+        * Simplified parsing: this constructor checks that every SET
+        * in the sequence of RDN has size exactly 1, and decodes each
+        * name element as a "generic string".
+        *
+        * On decoding error, an AsnException is thrown.
+        */
+       public X500Name(AsnElt aDN) : this(aDN, true)
+       {
+       }
+
+       /*
+        * Generic parsing. If 'strictStrings' is true, then the following
+        * rules are enforced:
+        * -- Every SET in the sequence of RDN must have size 1.
+        * -- Every name element is decoded as a string (by tag).
+        *
+        * If 'strictStrings' is false, then multiple elements may appear
+        * in each SET, and values needs not be decodable as string (values
+        * with a known OID must still be decodable).
+        *
+        * This constructor checks that within a single RDN, no two
+        * attributes may have the same type.
+        *
+        * On decoding error, an AsnException is thrown.
+        */
+       public X500Name(AsnElt aDN, bool strictStrings)
+       {
+               /*
+                * Note: the SEQUENCE tag MUST be present, since the
+                * ASN.1 definition of Name starts with a CHOICE; thus,
+                * any tag override would have to be explicit, not
+                * implicit.
+                */
+               aDN.CheckConstructed();
+               aDN.CheckTag(AsnElt.SEQUENCE);
+               List<List<DNPart>> r = new List<List<DNPart>>();
+               foreach (AsnElt aRDN in aDN.Sub) {
+                       aRDN.CheckConstructed();
+                       aRDN.CheckTag(AsnElt.SET);
+                       aRDN.CheckNumSubMin(1);
+                       int n = aRDN.Sub.Length;
+                       if (n != 1 && strictStrings) {
+                               throw new AsnException(String.Format(
+                                       "several ({0}) values in RDN", n));
+                       }
+                       List<DNPart> r2 = new List<DNPart>();
+                       r.Add(r2);
+                       for (int i = 0; i < n; i ++) {
+                               AsnElt aTV = aRDN.Sub[i];
+                               aTV.CheckConstructed();
+                               aTV.CheckTag(AsnElt.SEQUENCE);
+                               aTV.CheckNumSub(2);
+                               AsnElt aOID = aTV.GetSub(0);
+                               aOID.CheckTag(AsnElt.OBJECT_IDENTIFIER);
+                               AsnElt aVal = aTV.GetSub(1);
+                               string nt = aOID.GetOID();
+                               DNPart dnp = new DNPart(nt, aVal);
+                               if (strictStrings && !dnp.IsString) {
+                                       throw new AsnException(
+                                               "RDN is not a string");
+                               }
+                               r2.Add(dnp);
+                       }
+               }
+               Init(r);
+       }
+
+       /*
+        * Encode this DN into a string as specified in RFC 4514.
+        */
+       public override string ToString()
+       {
+               StringBuilder sb = new StringBuilder();
+               for (int i = PartsGen_.Length - 1; i >= 0; i --) {
+                       DNPart[] dd = PartsGen_[i];
+                       for (int j = 0; j < dd.Length; j ++) {
+                               if (j > 0) {
+                                       sb.Append("+");
+                               } else if (sb.Length > 0) {
+                                       sb.Append(",");
+                               }
+                               sb.Append(dd[j].ToString());
+                       }
+               }
+               return sb.ToString();
+       }
+
+       /*
+        * Encode back this DN into an ASN.1 structure.
+        */
+       public AsnElt ToAsn1()
+       {
+               AsnElt[] t1 = new AsnElt[PartsGen_.Length];
+               for (int i = 0; i < PartsGen_.Length; i ++) {
+                       DNPart[] dp = PartsGen_[i];
+                       AsnElt[] t2 = new AsnElt[dp.Length];
+                       for (int j = 0; j < dp.Length; j ++) {
+                               t2[j] = AsnElt.Make(AsnElt.SEQUENCE,
+                                       AsnElt.MakeOID(dp[j].OID),
+                                       dp[j].AsnValue);
+                       }
+                       t1[i] = AsnElt.MakeSetOf(t2);
+               }
+               return AsnElt.Make(AsnElt.SEQUENCE, t1);
+       }
+
+       /*
+        * Parse a string into a DN. The input is expected to use
+        * RFC 4514 format. Name elements that are provided as
+        * character strings will be mapped to ASN.1 PrintableString
+        * values (if they are compatible with that string type)
+        * or UTF8String values (otherwise).
+        *
+        * On parse error, an AsnException is thrown.
+        */
+       public static X500Name Parse(string str)
+       {
+               int n = str.Length;
+               int p = 0;
+               bool acc = false;
+               List<List<DNPart>> dn = new List<List<DNPart>>();
+               while (p < n) {
+                       /*
+                        * Find the next unescaped '+' or ',' sign.
+                        */
+                       bool lcwb = false;
+                       int q;
+                       for (q = p; q < n; q ++) {
+                               if (lcwb) {
+                                       lcwb = false;
+                                       continue;
+                               }
+                               switch (str[q]) {
+                               case ',':
+                               case '+':
+                                       goto found;
+                               case '\\':
+                                       lcwb = true;
+                                       break;
+                               }
+                       }
+               found:
+
+                       /*
+                        * Parse DN element.
+                        */
+                       DNPart dnp = DNPart.Parse(str.Substring(p, q - p));
+                       if (acc) {
+                               dn[dn.Count - 1].Add(dnp);
+                       } else {
+                               List<DNPart> r = new List<DNPart>();
+                               r.Add(dnp);
+                               dn.Add(r);
+                       }
+
+                       p = q + 1;
+                       acc = q < n && str[q] == '+';
+               }
+
+               dn.Reverse();
+               return new X500Name(dn);
+       }
+
+       /*
+        * Compare two DN for equality. "null" is equal to "null" but
+        * to nothing else.
+        */
+       public static bool Equals(X500Name dn1, X500Name dn2)
+       {
+               if (dn1 == null) {
+                       return dn2 == null;
+               } else {
+                       return dn1.Equals(dn2);
+               }
+       }
+
+       public override bool Equals(object obj)
+       {
+               return Equals(obj as X500Name);
+       }
+
+       public bool Equals(X500Name dn)
+       {
+               if (dn == null) {
+                       return false;
+               }
+               int n = PartsGen.Length;
+               if (dn.PartsGen.Length != n) {
+                       return false;
+               }
+               for (int i = 0; i < n; i ++) {
+                       DNPart[] p1 = PartsGen[i];
+                       DNPart[] p2 = dn.PartsGen[i];
+                       int k = p1.Length;
+                       if (k != p2.Length) {
+                               return false;
+                       }
+                       for (int j = 0; j < k; j ++) {
+                               if (!p1[j].Equals(p2[j])) {
+                                       return false;
+                               }
+                       }
+               }
+               return true;
+       }
+
+       public override int GetHashCode()
+       {
+               return hashCode;
+       }
+}
+
+}
diff --git a/XKeys/AlgorithmIdentifier.cs b/XKeys/AlgorithmIdentifier.cs
new file mode 100644 (file)
index 0000000..2d76e76
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+
+using Asn1;
+
+/*
+ * A wrapper class for an AlgorithmIdentifier (SEQUENCE of an OID
+ * then optional parameters).
+ */
+
+class AlgorithmIdentifier {
+
+       /*
+        * Get the OID that identifies the algorithm.
+        */
+       internal string OID {
+               get {
+                       return oid;
+               }
+       }
+
+       /*
+        * Get the algorithm parameters. This may be null if the
+        * structure did not contain parameters.
+        */
+       internal AsnElt Parameters {
+               get {
+                       return parameters;
+               }
+       }
+
+       string oid;
+       AsnElt parameters;
+
+       /*
+        * Create an instance over the provided ASN.1 element. The
+        * outer tag will be checked to match the universal tag for
+        * SEQUENCE.
+        */
+       internal AlgorithmIdentifier(AsnElt ai) : this(ai, true)
+       {
+       }
+
+       /*
+        * Create an instance over the provided ASN.1 element. If
+        * 'checkTag' is true, then the outer tag will be checked to
+        * match the universal tag for SEQUENCE. Set 'checkTag' to
+        * false if the tag was already checked, or if it has been
+        * overwritten with an implicit tag.
+        */
+       internal AlgorithmIdentifier(AsnElt ai, bool checkTag)
+       {
+               if (checkTag) {
+                       ai.CheckTag(AsnElt.SEQUENCE);
+               }
+               ai.CheckNumSubMin(1);
+               ai.CheckNumSubMax(2);
+               AsnElt ao = ai.GetSub(0);
+               ao.CheckTag(AsnElt.OBJECT_IDENTIFIER);
+               oid = ao.GetOID();
+               if (ai.Sub.Length >= 2) {
+                       parameters = ai.GetSub(1);
+               } else {
+                       parameters = null;
+               }
+       }
+
+       /*
+        * Create a new instance for a given OID, with no parameters.
+        */
+       internal AlgorithmIdentifier(string oid) : this(oid, null)
+       {
+       }
+
+       /*
+        * Create a new instance for a given OID, with the provided
+        * parameters (which may be null).
+        */
+       internal AlgorithmIdentifier(string oid, AsnElt parameters)
+       {
+               this.oid = oid;
+               this.parameters = parameters;
+       }
+
+       /*
+        * Encode this instance as a new ASN.1 object.
+        */
+       internal AsnElt ToAsn1()
+       {
+               AsnElt ao = AsnElt.MakeOID(oid);
+               if (parameters == null) {
+                       return AsnElt.Make(AsnElt.SEQUENCE, ao);
+               } else {
+                       return AsnElt.Make(AsnElt.SEQUENCE, ao, parameters);
+               }
+       }
+}
diff --git a/XKeys/KF.cs b/XKeys/KF.cs
new file mode 100644 (file)
index 0000000..2ce1100
--- /dev/null
@@ -0,0 +1,650 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.IO;
+using System.Text;
+
+using Asn1;
+using Crypto;
+
+namespace XKeys {
+
+/*
+ * The KF class contains static methods to decode and encode algorithm
+ * parameters, public keys and private keys.
+ */
+
+public class KF {
+
+       const string OID_RSA = "1.2.840.113549.1.1.1";
+       const string OID_RSA_OAEP = "1.2.840.113549.1.1.7";
+       const string OID_RSA_PSS = "1.2.840.113549.1.1.10";
+       const string OID_DSA = "1.2.840.10040.4.1";
+       const string OID_EC = "1.2.840.10045.2.1";
+
+       /*
+        * Encode the private key. If 'pk8' is true, then this uses
+        * PKCS#8 format (unencrypted); otherwise, it uses the
+        * "internal" format that does not specifically identify the key
+        * type.
+        */
+       public static byte[] EncodePrivateKey(IPrivateKey sk, bool pk8)
+       {
+               RSAPrivateKey rk = sk as RSAPrivateKey;
+               /* disabled DSA
+               DSAPrivateKey dk = sk as DSAPrivateKey;
+               */
+               ECPrivateKey ek = sk as ECPrivateKey;
+               if (rk != null) {
+                       AsnElt ark = AsnElt.Make(AsnElt.SEQUENCE,
+                               AsnElt.MakeInteger(0),
+                               AsnElt.MakeInteger(rk.N),
+                               AsnElt.MakeInteger(rk.E),
+                               AsnElt.MakeInteger(rk.D),
+                               AsnElt.MakeInteger(rk.P),
+                               AsnElt.MakeInteger(rk.Q),
+                               AsnElt.MakeInteger(rk.DP),
+                               AsnElt.MakeInteger(rk.DQ),
+                               AsnElt.MakeInteger(rk.IQ));
+                       byte[] enc = ark.Encode();
+                       if (pk8) {
+                               AsnElt apk8 = AsnElt.Make(AsnElt.SEQUENCE,
+                                       AsnElt.MakeInteger(0),
+                                       AsnElt.Make(AsnElt.SEQUENCE,
+                                               AsnElt.MakeOID(OID_RSA),
+                                               AsnElt.NULL_V),
+                                       AsnElt.MakeBlob(enc));
+                               enc = apk8.Encode();
+                       }
+                       return enc;
+               /* disabled DSA
+               } else if (dk != null) {
+                       if (pk8) {
+                               AsnElt adx = AsnElt.MakeInteger(dk.X);
+                               AsnElt adp = AsnElt.Make(AsnElt.SEQUENCE,
+                                       AsnElt.MakeInteger(dk.P),
+                                       AsnElt.MakeInteger(dk.Q),
+                                       AsnElt.MakeInteger(dk.G));
+                               AsnElt apk8 = AsnElt.Make(AsnElt.SEQUENCE,
+                                       AsnElt.MakeInteger(0),
+                                       AsnElt.Make(AsnElt.SEQUENCE,
+                                               AsnElt.MakeOID(OID_DSA),
+                                               adp),
+                                       AsnElt.MakeBlob(adx.Encode()));
+                               return apk8.Encode();
+                       } else {
+                               AsnElt adk = AsnElt.Make(AsnElt.SEQUENCE,
+                                       AsnElt.MakeInteger(0),
+                                       AsnElt.MakeInteger(dk.P),
+                                       AsnElt.MakeInteger(dk.Q),
+                                       AsnElt.MakeInteger(dk.G),
+                                       AsnElt.MakeInteger(dk.PublicKey.Y),
+                                       AsnElt.MakeInteger(dk.X));
+                               return adk.Encode();
+                       }
+               */
+               } else if (ek != null) {
+                       /*
+                        * The ECPrivateKey class guarantees that the
+                        * private key X is already encoded with the same
+                        * length as the subgroup order.
+                        * The ECPublicKey class provides the public key
+                        * as an already encoded point.
+                        */
+                       AsnElt acc = AsnElt.MakeOID(CurveToOID(ek.Curve));
+                       AsnElt apv = AsnElt.MakeExplicit(AsnElt.CONTEXT, 1,
+                               AsnElt.MakeBitString(ek.PublicKey.Pub));
+                       if (pk8) {
+                               AsnElt aek = AsnElt.Make(AsnElt.SEQUENCE,
+                                       AsnElt.MakeInteger(1),
+                                       AsnElt.MakeBlob(ek.X),
+                                       apv);
+                               AsnElt apk8 = AsnElt.Make(AsnElt.SEQUENCE,
+                                       AsnElt.MakeInteger(0),
+                                       AsnElt.Make(AsnElt.SEQUENCE,
+                                               AsnElt.MakeOID(OID_EC),
+                                               acc),
+                                       AsnElt.MakeBlob(aek.Encode()));
+                               return apk8.Encode();
+                       } else {
+                               AsnElt aek = AsnElt.Make(AsnElt.SEQUENCE,
+                                       AsnElt.MakeInteger(1),
+                                       AsnElt.MakeBlob(ek.X),
+                                       AsnElt.MakeExplicit(
+                                               AsnElt.CONTEXT, 0, acc),
+                                       apv);
+                               return aek.Encode();
+                       }
+               } else {
+                       if (sk == null) {
+                               throw new NullReferenceException();
+                       }
+                       throw new ArgumentException("Cannot encode "
+                               + sk.AlgorithmName + " private key");
+               }
+       }
+
+       /*
+        * Encode the private key into a PEM object. If 'pk8' is true,
+        * then unencrypted PKCS#8 format is used, and the PEM header
+        * is "BEGIN PRIVATE KEY"; otherwise, the "internal" private key
+        * format is used, and the PEM header identifies the key type.
+        *
+        * If 'crlf' is true, then PEM lines end with CR+LF; otherwise,
+        * they end with LF only.
+        */
+       public static string EncodePrivateKeyPEM(IPrivateKey sk,
+               bool pk8, bool crlf)
+       {
+               byte[] enc = EncodePrivateKey(sk, pk8);
+               string objType;
+               if (pk8) {
+                       objType = "PRIVATE KEY";
+               } else {
+                       objType = sk.AlgorithmName + " PRIVATE KEY";
+               }
+               return ToPEM(objType, enc, crlf ? "\r\n" : "\n");
+       }
+
+       /*
+        * Decode the provided private key. This method accepts both
+        * PKCS#8 and the "internal" format; the source object may be
+        * raw DER, Base64-encoded DER, or PEM. The key type is
+        * automatically detected.
+        */
+       public static IPrivateKey DecodePrivateKey(byte[] enc)
+       {
+               string pemType;
+               enc = AsnIO.FindBER(enc, false, out pemType);
+               if (enc == null) {
+                       throw new AsnException("Not an encoded object");
+               }
+               AsnElt ak = AsnElt.Decode(enc);
+               ak.CheckConstructed();
+               if (pemType != null) {
+                       switch (pemType) {
+                       case "RSA PRIVATE KEY":
+                               return DecodePrivateKeyRSA(ak);
+                       /* disabled DSA
+                       case "DSA PRIVATE KEY":
+                               return DecodePrivateKeyDSA(ak);
+                       */
+                       case "EC PRIVATE KEY":
+                               return DecodePrivateKeyEC(ak);
+                       case "PRIVATE KEY":
+                               return DecodePrivateKeyPKCS8(ak);
+                       default:
+                               throw new AsnException(
+                                       "Unknown PEM object: " + pemType);
+                       }
+               }
+               if (ak.Sub.Length == 3
+                       && ak.GetSub(0).TagValue == AsnElt.INTEGER
+                       && ak.GetSub(1).TagValue == AsnElt.SEQUENCE
+                       && ak.GetSub(2).TagValue == AsnElt.OCTET_STRING)
+               {
+                       return DecodePrivateKeyPKCS8(ak);
+               }
+               if (ak.Sub.Length >= 9) {
+                       bool mayBeRSA = true;
+                       for (int i = 0; i < 9; i ++) {
+                               if (ak.GetSub(i).TagValue != AsnElt.INTEGER) {
+                                       mayBeRSA = false;
+                                       break;
+                               }
+                       }
+                       if (mayBeRSA) {
+                               return DecodePrivateKeyRSA(ak);
+                       }
+               }
+               /* disabled DSA
+               if (ak.Sub.Length >= 6) {
+                       bool mayBeDSA = true;
+                       for (int i = 0; i < 6; i ++) {
+                               if (ak.GetSub(i).TagValue != AsnElt.INTEGER) {
+                                       mayBeDSA = false;
+                                       break;
+                               }
+                       }
+                       if (mayBeDSA) {
+                               return DecodePrivateKeyDSA(ak);
+                       }
+               }
+               */
+               if (ak.Sub.Length >= 2
+                       && ak.GetSub(0).TagValue == AsnElt.INTEGER
+                       && ak.GetSub(1).TagValue == AsnElt.OCTET_STRING)
+               {
+                       return DecodePrivateKeyEC(ak);
+               }
+               throw new AsnException("Unrecognized private key format");
+       }
+
+       static RSAPrivateKey DecodePrivateKeyRSA(AsnElt ak)
+       {
+               ak.CheckNumSubMin(9);
+               ak.GetSub(0).CheckTag(AsnElt.INTEGER);
+               long kt = ak.GetSub(0).GetInteger();
+               if (kt != 0) {
+                       throw new AsnException(
+                               "Unsupported RSA key type: " + kt);
+               }
+               ak.CheckNumSub(9);
+               return new RSAPrivateKey(
+                       GetPositiveInteger(ak.GetSub(1)),
+                       GetPositiveInteger(ak.GetSub(2)),
+                       GetPositiveInteger(ak.GetSub(3)),
+                       GetPositiveInteger(ak.GetSub(4)),
+                       GetPositiveInteger(ak.GetSub(5)),
+                       GetPositiveInteger(ak.GetSub(6)),
+                       GetPositiveInteger(ak.GetSub(7)),
+                       GetPositiveInteger(ak.GetSub(8)));
+       }
+
+       /* disabled DSA
+       static DSAPrivateKey DecodePrivateKeyDSA(AsnElt ak)
+       {
+               ak.CheckNumSubMin(6);
+               for (int i = 0; i < 6; i ++) {
+                       ak.GetSub(i).CheckTag(AsnElt.INTEGER);
+               }
+               long kt = ak.GetSub(0).GetInteger();
+               if (kt != 0) {
+                       throw new AsnException(
+                               "Unsupported DSA key type: " + kt);
+               }
+               DSAPrivateKey dsk = new DSAPrivateKey(
+                       GetPositiveInteger(ak.GetSub(1)),
+                       GetPositiveInteger(ak.GetSub(2)),
+                       GetPositiveInteger(ak.GetSub(3)),
+                       GetPositiveInteger(ak.GetSub(5)));
+               DSAPublicKey dpk = dsk.PublicKey;
+               if (BigInt.Compare(dpk.Y,
+                       GetPositiveInteger(ak.GetSub(4))) != 0)
+               {
+                       throw new CryptoException(
+                               "DSA key pair public/private mismatch");
+               }
+               return dsk;
+       }
+       */
+
+       static ECPrivateKey DecodePrivateKeyEC(AsnElt ak)
+       {
+               return DecodePrivateKeyEC(ak, null);
+       }
+
+       static ECPrivateKey DecodePrivateKeyEC(AsnElt ak, ECCurve curve)
+       {
+               ak.CheckNumSubMin(2);
+               ak.GetSub(0).CheckTag(AsnElt.INTEGER);
+               ak.GetSub(1).CheckTag(AsnElt.OCTET_STRING);
+               long kt = ak.GetSub(0).GetInteger();
+               if (kt != 1) {
+                       throw new AsnException(
+                               "Unsupported EC key type: " + kt);
+               }
+               byte[] x = ak.GetSub(1).CopyValue();
+               byte[] pub = null;
+               int n = ak.Sub.Length;
+               int p = 2;
+               if (p < n) {
+                       AsnElt acc = ak.GetSub(p);
+                       if (acc.TagClass == AsnElt.CONTEXT
+                               && acc.TagValue == 0)
+                       {
+                               acc.CheckNumSub(1);
+                               acc = acc.GetSub(0);
+                               ECCurve curve2 = DecodeCurve(acc);
+
+                               /*
+                                * Here, we support only named curves.
+                                */
+                               /* obsolete
+                               */
+                               if (curve == null) {
+                                       curve = curve2;
+                               } else if (!curve.Equals(curve2)) {
+                                       throw new AsnException(string.Format(
+                                               "Inconsistent curve"
+                                               + " specification ({0} / {1})",
+                                               curve.Name, curve2.Name));
+                               }
+
+                               p ++;
+                       }
+               }
+               if (p < n) {
+                       AsnElt acc = ak.GetSub(p);
+                       if (acc.TagClass == AsnElt.CONTEXT
+                               && acc.TagValue == 1)
+                       {
+                               acc.CheckNumSub(1);
+                               acc = acc.GetSub(0);
+                               acc.CheckTag(AsnElt.BIT_STRING);
+                               pub = acc.GetBitString();
+                       }
+               }
+
+               if (curve == null) {
+                       throw new AsnException("No curve specified for EC key");
+               }
+               ECPrivateKey esk = new ECPrivateKey(curve, x);
+               if (pub != null) {
+                       ECPublicKey epk = new ECPublicKey(curve, pub);
+                       if (!epk.Equals(esk.PublicKey)) {
+                               throw new CryptoException(
+                                       "EC key pair public/private mismatch");
+                       }
+               }
+               return esk;
+       }
+
+       static ECCurve DecodeCurve(AsnElt acc)
+       {
+               /*
+                * We support only named curves for now. PKIX does not
+                * want to see any other kind of curve anyway (see RFC
+                * 5480).
+                */
+               acc.CheckTag(AsnElt.OBJECT_IDENTIFIER);
+               string oid = acc.GetOID();
+               return OIDToCurve(oid);
+       }
+
+       static IPrivateKey DecodePrivateKeyPKCS8(AsnElt ak)
+       {
+               ak.CheckNumSub(3);
+               ak.GetSub(0).CheckTag(AsnElt.INTEGER);
+               long v = ak.GetSub(0).GetInteger();
+               if (v != 0) {
+                       throw new AsnException(
+                               "Unsupported PKCS#8 version: " + v);
+               }
+               AsnElt aai = ak.GetSub(1);
+               aai.CheckTag(AsnElt.SEQUENCE);
+               aai.CheckNumSubMin(1);
+               aai.CheckNumSubMin(2);
+               aai.GetSub(0).CheckTag(AsnElt.OBJECT_IDENTIFIER);
+               string oid = aai.GetSub(0).GetOID();
+               ak.GetSub(2).CheckTag(AsnElt.OCTET_STRING);
+               byte[] rawKey = ak.GetSub(2).CopyValue();
+               AsnElt ark = AsnElt.Decode(rawKey);
+
+               switch (oid) {
+               case OID_RSA:
+               case OID_RSA_OAEP:
+               case OID_RSA_PSS:
+                       return DecodePrivateKeyRSA(ark);
+               /* disabled DSA
+               case OID_DSA:
+                       return DecodePrivateKeyDSA(ark);
+               */
+               case OID_EC:
+                       /*
+                        * For elliptic curves, the parameters may
+                        * include the curve specification.
+                        */
+                       ECCurve curve = (aai.Sub.Length == 2)
+                               ? DecodeCurve(aai.GetSub(1)) : null;
+                       return DecodePrivateKeyEC(ark, curve);
+               default:
+                       throw new AsnException(
+                               "Unknown PKCS#8 key type: " + oid);
+               }
+       }
+
+       /*
+        * Encode a public key as a SubjectPublicKeyInfo structure.
+        */
+       public static AsnElt EncodePublicKey(IPublicKey pk)
+       {
+               RSAPublicKey rk = pk as RSAPublicKey;
+               /* disabled DSA
+               DSAPublicKey dk = pk as DSAPublicKey;
+               */
+               ECPublicKey ek = pk as ECPublicKey;
+               string oid;
+               AsnElt app;
+               byte[] pkv;
+               if (rk != null) {
+                       oid = OID_RSA;
+                       app = AsnElt.NULL_V;
+                       pkv = AsnElt.Make(AsnElt.SEQUENCE,
+                               AsnElt.MakeInteger(rk.Modulus),
+                               AsnElt.MakeInteger(rk.Exponent)).Encode();
+               /* disabled DSA
+               } else if (dk != null) {
+                       oid = OID_DSA;
+                       app = AsnElt.Make(AsnElt.SEQUENCE,
+                               AsnElt.MakeInteger(dk.P),
+                               AsnElt.MakeInteger(dk.Q),
+                               AsnElt.MakeInteger(dk.G));
+                       pkv = AsnElt.MakeInteger(dk.Y).Encode();
+               */
+               } else if (ek != null) {
+                       oid = OID_EC;
+                       app = AsnElt.MakeOID(CurveToOID(ek.Curve));
+                       pkv = ek.Pub;
+               } else {
+                       throw new ArgumentException(
+                               "Cannot encode key type: " + pk.AlgorithmName);
+               }
+               AsnElt ai;
+               if (app == null) {
+                       ai = AsnElt.Make(AsnElt.SEQUENCE,
+                               AsnElt.MakeOID(oid));
+               } else {
+                       ai = AsnElt.Make(AsnElt.SEQUENCE,
+                               AsnElt.MakeOID(oid),
+                               app);
+               }
+               return AsnElt.Make(AsnElt.SEQUENCE,
+                       ai,
+                       AsnElt.MakeBitString(pkv));
+       }
+
+       /*
+        * Decode a public key (SubjectPublicKeyInfo).
+        */
+       public static IPublicKey DecodePublicKey(byte[] spki)
+       {
+               string pemType = null;
+               spki = AsnIO.FindBER(spki, false, out pemType);
+               if (spki == null) {
+                       throw new AsnException("Not an encoded object");
+               }
+               return DecodePublicKey(AsnElt.Decode(spki));
+       }
+
+       /*
+        * Decode a public key (SubjectPublicKeyInfo).
+        */
+       public static IPublicKey DecodePublicKey(AsnElt ak)
+       {
+               ak.CheckNumSub(2);
+               AlgorithmIdentifier ai = new AlgorithmIdentifier(ak.GetSub(0));
+               AsnElt abs = ak.GetSub(1);
+               abs.CheckTag(AsnElt.BIT_STRING);
+               byte[] pub = abs.GetBitString();
+               switch (ai.OID) {
+               case OID_RSA:
+               case OID_RSA_OAEP:
+               case OID_RSA_PSS:
+                       return DecodePublicKeyRSA(pub);
+               /* disabled DSA
+               case OID_DSA:
+                       return DecodePublicKeyDSA(pub);
+               */
+               case OID_EC:
+                       /*
+                        * For elliptic curves, the parameters should
+                        * include the curve specification.
+                        */
+                       AsnElt ap = ai.Parameters;
+                       if (ap == null) {
+                               throw new AsnException("No curve specified"
+                                       + " for EC public key");
+                       }
+                       if (ap.TagClass != AsnElt.UNIVERSAL
+                               || ap.TagValue != AsnElt.OBJECT_IDENTIFIER)
+                       {
+                               throw new AsnException("Unsupported type"
+                                       + " of curve specification");
+                       }
+                       return new ECPublicKey(OIDToCurve(ap.GetOID()), pub);
+               default:
+                       throw new AsnException(
+                               "Unknown public key type: " + ai.OID);
+               }
+       }
+
+       static IPublicKey DecodePublicKeyRSA(byte[] pub)
+       {
+               AsnElt ae = AsnElt.Decode(pub);
+               ae.CheckTag(AsnElt.SEQUENCE);
+               ae.CheckNumSub(2);
+               byte[] n = GetPositiveInteger(ae.GetSub(0));
+               byte[] e = GetPositiveInteger(ae.GetSub(1));
+               return new RSAPublicKey(n, e);
+       }
+
+       static string CurveToOID(ECCurve curve)
+       {
+               switch (curve.Name) {
+               case "P-192":
+                       return "1.2.840.10045.3.1.1";
+               case "P-224":
+                       return "1.3.132.0.33";
+               case "P-256":
+                       return "1.2.840.10045.3.1.7";
+               case "P-384":
+                       return "1.3.132.0.34";
+               case "P-521":
+                       return "1.3.132.0.35";
+               }
+               throw new ArgumentException(string.Format(
+                       "No known OID for curve '{0}'", curve.Name));
+       }
+
+       static ECCurve OIDToCurve(string oid)
+       {
+               switch (oid) {
+               /*
+               case "1.2.840.10045.3.1.1":
+                       return NIST.P192;
+               case "1.3.132.0.33":
+                       return NIST.P224;
+               */
+               case "1.2.840.10045.3.1.7":
+                       return NIST.P256;
+               case "1.3.132.0.34":
+                       return NIST.P384;
+               case "1.3.132.0.35":
+                       return NIST.P521;
+               }
+               throw new ArgumentException(string.Format(
+                       "No known curve for OID {0}", oid));
+       }
+
+       static byte[] GetPositiveInteger(AsnElt ae)
+       {
+               ae.CheckTag(AsnElt.INTEGER);
+               byte[] x = ae.CopyValue();
+               if (x.Length == 0) {
+                       throw new AsnException("Invalid integer (empty)");
+               }
+               if (x[0] >= 0x80) {
+                       throw new AsnException("Invalid integer (negative)");
+               }
+               return x;
+       }
+
+       static int Dec16be(byte[] buf, int off)
+       {
+               return (buf[off] << 8) + buf[off + 1];
+       }
+
+       static int Dec24be(byte[] buf, int off)
+       {
+               return (buf[off] << 16) + (buf[off + 1] << 8) + buf[off + 2];
+       }
+
+       const string B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+               + "abcdefghijklmnopqrstuvwxyz0123456789+/";
+
+       public static string ToBase64(byte[] buf, int off, int len)
+       {
+               char[] tc = new char[((len + 2) / 3) << 2];
+               for (int i = 0, j = 0; i < len; i += 3) {
+                       if ((i + 3) <= len) {
+                               int x = Dec24be(buf, off + i);
+                               tc[j ++] = B64[x >> 18];
+                               tc[j ++] = B64[(x >> 12) & 0x3F];
+                               tc[j ++] = B64[(x >> 6) & 0x3F];
+                               tc[j ++] = B64[x & 0x3F];
+                       } else if ((i + 2) == len) {
+                               int x = Dec16be(buf, off + i);
+                               tc[j ++] = B64[x >> 10];
+                               tc[j ++] = B64[(x >> 4) & 0x3F];
+                               tc[j ++] = B64[(x << 2) & 0x3F];
+                               tc[j ++] = '=';
+                       } else if ((i + 1) == len) {
+                               int x = buf[off + i];
+                               tc[j ++] = B64[(x >> 2) & 0x3F];
+                               tc[j ++] = B64[(x << 4) & 0x3F];
+                               tc[j ++] = '=';
+                               tc[j ++] = '=';
+                       }
+               }
+               return new string(tc);
+       }
+
+       public static void WritePEM(TextWriter w, string objType, byte[] buf)
+       {
+               w.WriteLine("-----BEGIN {0}-----", objType.ToUpperInvariant());
+               int n = buf.Length;
+               for (int i = 0; i < n; i += 57) {
+                       int len = Math.Min(57, n - i);
+                       w.WriteLine(ToBase64(buf, i, len));
+               }
+               w.WriteLine("-----END {0}-----", objType.ToUpperInvariant());
+       }
+
+       public static string ToPEM(string objType, byte[] buf)
+       {
+               return ToPEM(objType, buf, "\n");
+       }
+
+       public static string ToPEM(string objType, byte[] buf, string nl)
+       {
+               StringWriter w = new StringWriter();
+               w.NewLine = nl;
+               WritePEM(w, objType, buf);
+               return w.ToString();
+       }
+}
+
+}
diff --git a/ZInt/ZInt.cs b/ZInt/ZInt.cs
new file mode 100644 (file)
index 0000000..c8333f4
--- /dev/null
@@ -0,0 +1,3250 @@
+/*
+ * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Security.Cryptography;
+using System.Text;
+
+/*
+ * A custom "big integer" implementation. It internally uses an array
+ * of 32-bit integers, that encode the integer in little-endian convention,
+ * using two's complement for negative integers.
+ *
+ * Apart from the array, a single 32-bit field is also present, which
+ * encodes the sign. When the value is small (it fits on 32 bits), then
+ * the array pointer is null, and the value is in the 32-bit field.
+ * Since ZInt is a struct, this means that computations using ZInt do
+ * not entail any dynamic (GC-based) memory allocation as long as the
+ * value fits on 32 bits. This makes it substantially faster than usual
+ * "big integer" implementations (including .NET's implementation, since
+ * version 4.0) when values are small.
+ *
+ * Instances are immutable, and thus can be used as if they were
+ * "plain integers".
+ *
+ * None of this code is "constant-time". As such, ZInt should be
+ * considered unsuitable to implementations of cryptographic algorithms.
+ */
+
+public struct ZInt : IComparable, IComparable<ZInt>, IEquatable<ZInt> {
+
+       /*
+        * CONVENTIONS:
+        *
+        * If varray == null, then "small" contains the integer value.
+        *
+        * If varray != null, then it contains the value, in little-endian
+        * convention (least significant word comes first) and of
+        * minimal encoded length (i.e. "trimmed"). Two's complement is
+        * used for negative values. "small" is then -1 or 0, depending
+        * on whether the value is negative or not.
+        *
+        * Note that the trimmed value does not always include the sign
+        * bit.
+        *
+        * If the integer value is in the range of values which can be
+        * represented in an "int", then magn is null. There is no allowed
+        * overlap between the two kinds of encodings.
+        *
+        * Default value thus encodes the integer zero.
+        */
+
+       readonly int small;
+       readonly uint[] varray;
+
+       /*
+        * The value -1.
+        */
+       public static ZInt MinusOne {
+               get { return new ZInt(-1, null); }
+       }
+
+       /*
+        * The value 0.
+        */
+       public static ZInt Zero {
+               get { return new ZInt(0, null); }
+       }
+
+       /*
+        * The value 1.
+        */
+       public static ZInt One {
+               get { return new ZInt(1, null); }
+       }
+
+       /*
+        * The value 2.
+        */
+       public static ZInt Two {
+               get { return new ZInt(2, null); }
+       }
+
+       /*
+        * Internal constructor which assumes that the provided values are
+        * correct and normalized.
+        */
+       private ZInt(int small, uint[] varray)
+       {
+               this.small = small;
+               this.varray = varray;
+#if DEBUG
+               if (varray != null) {
+                       if (small != -1 && small != 0) {
+                               throw new Exception(
+                                       "Bad sign encoding: " + small);
+                       }
+                       if (varray.Length == 0) {
+                               throw new Exception("Empty varray");
+                       }
+                       if (Length(small, varray) != varray.Length) {
+                               throw new Exception("Untrimmed varray");
+                       }
+                       if (varray.Length == 1) {
+                               /*
+                                * If there was room for a sign bit, then
+                                * the "small" encoding should have been used.
+                                */
+                               if (((varray[0] ^ (uint)small) >> 31) == 0) {
+                                       throw new Exception(
+                                               "suboptimal encoding");
+                               }
+                       }
+               }
+#endif
+       }
+
+       /*
+        * Main internal build method. This method normalizes the encoding
+        * ("small" encoding is used when possible; otherwise, the varray
+        * is trimmed and the sign is normalized to -1 or 0).
+        *
+        * If "varray" is null, then the small value is used. Otherwise,
+        * only the sign bit (most significant bit) of "small" is used,
+        * and that value is normalized; the array is trimmed. If the
+        * small encoding then becomes applicable, then it is used.
+        */
+       private static ZInt Make(int small, uint[] varray)
+       {
+               if (varray == null) {
+                       return new ZInt(small, null);
+               }
+               small >>= 31;
+               int n = Length(small, varray);
+               if (n == 1 && (((varray[0] ^ (uint)small) >> 31) == 0)) {
+                       small = (int)varray[0];
+                       varray = null;
+               } else {
+                       /*
+                        * Note: if n == 0 then the value is -1 or 0, and
+                        * "small" already contains the correct value;
+                        * Trim() will then return null, which is appropriate.
+                        */
+                       varray = Trim(small, varray, n);
+               }
+               return new ZInt(small, varray);
+       }
+
+       /*
+        * Create an instance from a signed 32-bit integer.
+        */
+       public ZInt(int val)
+       {
+               small = val;
+               varray = null;
+       }
+
+       /*
+        * Create an instance from an unsigned 32-bit integer.
+        */
+       public ZInt(uint val)
+       {
+               small = (int)val;
+               if (small < 0) {
+                       small = 0;
+                       varray = new uint[1] { val };
+               } else {
+                       varray = null;
+               }
+       }
+
+       /*
+        * Create an instance from a signed 64-bit integer.
+        */
+       public ZInt(long val)
+       {
+               small = (int)val;
+               if ((long)small == val) {
+                       varray = null;
+               } else {
+                       ulong uval = (ulong)val;
+                       uint w0 = (uint)uval;
+                       uint w1 = (uint)(uval >> 32);
+                       if (w1 == 0) {
+                               small = 0;
+                               varray = new uint[1] { w0 };
+                       } else if (w1 == 0xFFFFFFFF) {
+                               small = -1;
+                               varray = new uint[1] { w0 };
+                       } else {
+                               small = (int)w1 >> 31;
+                               varray = new uint[2] { w0, w1 };
+                       }
+               }
+       }
+
+       /*
+        * Create an instance from an unsigned 64-bit integer.
+        */
+       public ZInt(ulong val)
+       {
+               if (val <= 0x7FFFFFFF) {
+                       small = (int)val;
+                       varray = null;
+               } else {
+                       small = 0;
+                       uint w0 = (uint)val;
+                       uint w1 = (uint)(val >> 32);
+                       if (w1 == 0) {
+                               varray = new uint[1] { w0 };
+                       } else {
+                               varray = new uint[2] { w0, w1 };
+                       }
+               }
+       }
+
+       /*
+        * Create a ZInt instance for an integer expressed as an array
+        * of 32-bit integers (unsigned little-endian convention), with
+        * a specific number of value bits.
+        */
+       public static ZInt Make(uint[] words, int numBits)
+       {
+               if (numBits == 0) {
+                       return Zero;
+               }
+               int n = (numBits + 31) >> 5;
+               int kb = numBits & 31;
+               uint[] m = new uint[n];
+               Array.Copy(words, 0, m, 0, n);
+               if (kb > 0) {
+                       m[n - 1] &= ~((uint)0xFFFFFFFF << kb);
+               }
+               return Make(0, m);
+       }
+
+       /*
+        * Create a ZInt instance for an integer expressed as an array
+        * of 64-bit integers (unsigned little-endian convention), with
+        * a specific number of value bits.
+        */
+       public static ZInt Make(ulong[] words, int numBits)
+       {
+               if (numBits == 0) {
+                       return Zero;
+               }
+               int kw = (numBits + 63) >> 6;
+               int kb = numBits & 63;
+               int n = kw * 2;
+               uint[] m = new uint[n];
+               if (kb != 0) {
+                       ulong z = words[kw - 1]
+                               & ~((ulong)0xFFFFFFFFFFFFFFFF << kb);
+                       m[n - 1] = (uint)(z >> 32);
+                       m[n - 2] = (uint)z;
+                       kw --;
+                       n -= 2;
+               }
+               for (int i = kw - 1, j = n - 2; i >= 0; i --, j -= 2) {
+                       ulong z = words[i];
+                       m[j + 0] = (uint)z;
+                       m[j + 1] = (uint)(z >> 32);
+               }
+               return Make(0, m);
+       }
+
+       /*
+        * Test whether this value is 0.
+        */
+       public bool IsZero {
+               get {
+                       return small == 0 && varray == null;
+               }
+       }
+
+       /*
+        * Test whether this value is 1.
+        */
+       public bool IsOne {
+               get {
+                       return small == 1;
+               }
+       }
+
+       /*
+        * Test whether this value is even.
+        */
+       public bool IsEven {
+               get {
+                       uint w = (varray == null) ? (uint)small : varray[0];
+                       return (w & 1) == 0;
+               }
+       }
+
+       /*
+        * Test whether this value is a power of 2. Note that 1 is a power
+        * of 2 (but 0 is not). For negative values, false is returned.
+        */
+       public bool IsPowerOfTwo {
+               get {
+                       if (small < 0) {
+                               return false;
+                       }
+                       if (varray == null) {
+                               return small != 0 && (small & -small) == small;
+                       }
+                       int n = varray.Length;
+                       int z = (int)varray[n - 1];
+                       if ((z & -z) != z) {
+                               return false;
+                       }
+                       for (int i = n - 2; i >= 0; i --) {
+                               if (varray[i] != 0) {
+                                       return false;
+                               }
+                       }
+                       return true;
+               }
+       }
+
+       /*
+        * Get the sign of this value as an integer (-1 for negative
+        * values, 1 for positive, 0 for zero).
+        */
+       public int Sign {
+               get {
+                       if (varray == null) {
+                               if (small < 0) {
+                                       return -1;
+                               } else if (small == 0) {
+                                       return 0;
+                               } else {
+                                       return 1;
+                               }
+                       } else {
+                               return small | 1;
+                       }
+               }
+       }
+
+       /*
+        * Test whether this value would fit in an 'int'.
+        */
+       public bool IsInt {
+               get {
+                       return varray == null;
+               }
+       }
+
+       /*
+        * Test whether this value would fit in a "long".
+        */
+       public bool IsLong {
+               get {
+                       if (varray == null) {
+                               return true;
+                       }
+                       int n = varray.Length;
+                       if (n == 1) {
+                               return true;
+                       } else if (n == 2) {
+                               return ((int)varray[1] >> 31) == small;
+                       } else {
+                               return false;
+                       }
+               }
+       }
+
+       /*
+        * Get the length of the value in bits. This is the minimal number
+        * of bits of the two's complement representation of the value,
+        * excluding the sign bit; thus, both 0 and -1 have bit length 0.
+        */
+       public int BitLength {
+               get {
+                       if (varray == null) {
+                               if (small < 0) {
+                                       return 32 - LeadingZeros(~(uint)small);
+                               } else {
+                                       return 32 - LeadingZeros((uint)small);
+                               }
+                       } else {
+                               int n = varray.Length;
+                               int bl = n << 5;
+                               uint w = varray[n - 1];
+                               if (small < 0) {
+                                       return bl - LeadingZeros(~w);
+                               } else {
+                                       return bl - LeadingZeros(w);
+                               }
+                       }
+               }
+       }
+
+       /*
+        * Test whether the specified bit has value 1 or 0 ("true" is
+        * returned if the bit has value 1). Note that for negative values,
+        * two's complement representation is assumed.
+        */
+       public bool TestBit(int n)
+       {
+               if (n < 0) {
+                       throw new ArgumentOutOfRangeException();
+               }
+               if (varray == null) {
+                       if (n >= 32) {
+                               return small < 0;
+                       } else {
+                               return (((uint)small >> n) & (uint)1) != 0;
+                       }
+               } else {
+                       int nw = n >> 5;
+                       if (nw >= varray.Length) {
+                               return small != 0;
+                       }
+                       int nb = n & 31;
+                       return ((varray[nw] >> nb) & (uint)1) != 0;
+               }
+       }
+
+       /*
+        * Copy some bits from this instance to the provided array. Bits
+        * are copied in little-endian order. First bit to be copied is
+        * bit at index "off", and exactly "num" bits are copied. This
+        * method modifies only the minimum number of destination words
+        * (i.e. the first "(num+31)/32" words, exactly). Remaining bits
+        * in the last touched word are set to 0.
+        */
+       public void CopyBits(int off, int num, uint[] dest)
+       {
+               CopyBits(off, num, dest, 0);
+       }
+
+       public void CopyBits(int off, int num, uint[] dest, int destOff)
+       {
+               if (off < 0 || num < 0) {
+                       throw new ArgumentOutOfRangeException();
+               }
+               if (num == 0) {
+                       return;
+               }
+               ZInt x = this;
+               if (off > 0) {
+                       x >>= off;
+               }
+               int kw = num >> 5;
+               int kb = num & 31;
+               uint hmask = ~((uint)0xFFFFFFFF << kb);
+               if (x.varray == null) {
+                       if (kw == 0) {
+                               dest[destOff] = (uint)x.small & hmask;
+                       } else {
+                               uint iw = (uint)(x.small >> 31);
+                               dest[destOff] = (uint)x.small;
+                               for (int i = 1; i < kw; i ++) {
+                                       dest[destOff + i] = iw;
+                               }
+                               if (kb > 0) {
+                                       dest[destOff + kw] = iw & hmask;
+                               }
+                       }
+               } else {
+                       int n = x.varray.Length;
+                       if (kw <= n) {
+                               Array.Copy(x.varray, 0, dest, destOff, kw);
+                       } else {
+                               Array.Copy(x.varray, 0, dest, destOff, n);
+                               for (int i = n; i < kw; i ++) {
+                                       dest[destOff + i] = (uint)x.small;
+                               }
+                       }
+                       if (kb > 0) {
+                               uint last;
+                               if (kw < n) {
+                                       last = x.varray[kw] & hmask;
+                               } else {
+                                       last = (uint)x.small & hmask;
+                               }
+                               dest[destOff + kw] = last;
+                       }
+               }
+       }
+
+       /*
+        * Copy some bits from this instance to the provided array. Bits
+        * are copied in little-endian order. First bit to be copied is
+        * bit at index "off", and exactly "num" bits are copied. This
+        * method modifies only the minimum number of destination words
+        * (i.e. the first "(num+63)/64" words, exactly). Remaining bits
+        * in the last touched word are set to 0.
+        */
+       public void CopyBits(int off, int num, ulong[] dest)
+       {
+               CopyBits(off, num, dest, 0);
+       }
+
+       public void CopyBits(int off, int num, ulong[] dest, int destOff)
+       {
+               if (off < 0 || num < 0) {
+                       throw new ArgumentOutOfRangeException();
+               }
+               if (num == 0) {
+                       return;
+               }
+               ZInt x = this;
+               if (off > 0) {
+                       x >>= off;
+               }
+               int kw = num >> 6;
+               int kb = num & 63;
+               ulong hmask = ~((ulong)0xFFFFFFFFFFFFFFFF << kb);
+               long xs = (long)x.small;
+               if (x.varray == null) {
+                       if (kw == 0) {
+                               dest[destOff] = (ulong)xs & hmask;
+                       } else {
+                               ulong iw = (ulong)(xs >> 31);
+                               dest[destOff] = (ulong)xs;
+                               for (int i = 1; i < kw; i ++) {
+                                       dest[destOff + i] = iw;
+                               }
+                               if (kb > 0) {
+                                       dest[destOff + kw] = iw & hmask;
+                               }
+                       }
+               } else {
+                       int n = x.varray.Length;
+                       uint iw = (uint)x.small;
+                       int j = 0;
+                       for (int i = 0; i < kw; i ++, j += 2) {
+                               uint w0 = (j < n) ? x.varray[j] : iw;
+                               uint w1 = ((j + 1) < n) ? x.varray[j + 1] : iw;
+                               dest[destOff + i] =
+                                       (ulong)w0 | ((ulong)w1 << 32);
+                       }
+                       if (kb > 0) {
+                               uint w0 = (j < n) ? x.varray[j] : iw;
+                               uint w1 = ((j + 1) < n) ? x.varray[j + 1] : iw;
+                               ulong last = (ulong)w0 | ((ulong)w1 << 32);
+                               dest[destOff + kw] = last & hmask;
+                       }
+               }
+       }
+
+       /*
+        * Extract a 32-bit word at a given offset (counted in bits).
+        * This function is equivalent to right-shifting the value by
+        * "off" bits, then returning the low 32 bits (however, this
+        * function may be more efficient).
+        */
+       public uint GetWord(int off)
+       {
+               if (off < 0) {
+                       throw new ArgumentOutOfRangeException();
+               }
+               if (varray == null) {
+                       int x = small;
+                       if (off >= 32) {
+                               off = 31;
+                       }
+                       return (uint)(x >> off);
+               }
+               int n = varray.Length;
+               int kw = off >> 5;
+               if (kw >= n) {
+                       return (uint)small;
+               }
+               int kb = off & 31;
+               if (kb == 0) {
+                       return varray[kw];
+               } else {
+                       uint hi;
+                       if (kw == n - 1) {
+                               hi = (uint)small;
+                       } else {
+                               hi = varray[kw + 1];
+                       }
+                       uint lo = varray[kw];
+                       return (lo >> kb) | (hi << (32 - kb));
+               }
+       }
+
+       /*
+        * Extract a 64-bit word at a given offset (counted in bits).
+        * This function is equivalent to right-shifting the value by
+        * "off" bits, then returning the low 64 bits (however, this
+        * function may be more efficient).
+        */
+       public ulong GetWord64(int off)
+       {
+               if (off < 0) {
+                       throw new ArgumentOutOfRangeException();
+               }
+               if (varray == null) {
+                       int x = small;
+                       if (off >= 32) {
+                               off = 31;
+                       }
+                       return (ulong)(x >> off);
+               }
+               int n = varray.Length;
+               int kw = off >> 5;
+               if (kw >= n) {
+                       return (ulong)small;
+               }
+               int kb = off & 31;
+               if (kb == 0) {
+                       if (kw == (n - 1)) {
+                               return (ulong)varray[kw]
+                                       | ((ulong)small << 32);
+                       } else {
+                               return (ulong)varray[kw]
+                                       | ((ulong)varray[kw + 1] << 32);
+                       }
+               } else {
+                       uint v0, v1, v2;
+                       if (kw == (n - 1)) {
+                               v0 = varray[kw];
+                               v1 = (uint)small;
+                               v2 = (uint)small;
+                       } else if (kw == (n - 2)) {
+                               v0 = varray[kw];
+                               v1 = varray[kw + 1];
+                               v2 = (uint)small;
+                       } else {
+                               v0 = varray[kw];
+                               v1 = varray[kw + 1];
+                               v2 = varray[kw + 2];
+                       }
+                       uint lo = (v0 >> kb) | (v1 << (32 - kb));
+                       uint hi = (v1 >> kb) | (v2 << (32 - kb));
+                       return (ulong)lo | ((ulong)hi << 32);
+               }
+       }
+
+       /*
+        * Convert this value to an 'int', using silent truncation if
+        * the value does not fit.
+        */
+       public int ToInt {
+               get {
+                       return (varray == null) ? small : (int)varray[0];
+               }
+       }
+
+       /*
+        * Convert this value to an 'uint', using silent truncation if
+        * the value does not fit.
+        */
+       public uint ToUInt {
+               get {
+                       return (varray == null) ? (uint)small : varray[0];
+               }
+       }
+
+       /*
+        * Convert this value to a 'long', using silent truncation if
+        * the value does not fit.
+        */
+       public long ToLong {
+               get {
+                       return (long)ToULong;
+               }
+       }
+
+       /*
+        * Convert this value to an 'ulong', using silent truncation if
+        * the value does not fit.
+        */
+       public ulong ToULong {
+               get {
+                       if (varray == null) {
+                               return (ulong)small;
+                       } else if (varray.Length == 1) {
+                               uint iw = (uint)small;
+                               return (ulong)varray[0] | ((ulong)iw << 32);
+                       } else {
+                               return (ulong)varray[0]
+                                       | ((ulong)varray[1] << 32);
+                       }
+               }
+       }
+
+       /*
+        * Get the actual length of a varray encoding: this is the minimal
+        * length, in words, needed to encode the value. The value sign
+        * is provided as a negative or non-negative integer, and the
+        * encoding of minimal length does not necessarily include a
+        * sign bit. The value 0 is returned when the array encodes 0
+        * or -1 (depending on sign).
+        */
+       static int Length(int sign, uint[] m)
+       {
+               if (m == null) {
+                       return 0;
+               }
+               uint sw = (uint)(sign >> 31);
+               int n = m.Length;
+               while (n > 0 && m[n - 1] == sw) {
+                       n --;
+               }
+               return n;
+       }
+
+       /*
+        * Trim an encoding to its minimal encoded length. If the provided
+        * array is already of minimal length, it is returned unchanged.
+        */
+       static uint[] Trim(int sign, uint[] m)
+       {
+               int n = Length(sign, m);
+               if (n == 0) {
+                       return null;
+               } else if (n < m.Length) {
+                       uint[] mt = new uint[n];
+                       Array.Copy(m, 0, mt, 0, n);
+                       return mt;
+               } else {
+                       return m;
+               }
+       }
+
+       /*
+        * Trim or extend a value to the provided length. The returned
+        * array will have the length specified as "n" (if n == 0, then
+        * null is returned). If the source array already has the right
+        * size, then it is returned unchanged.
+        */
+       static uint[] Trim(int sign, uint[] m, int n)
+       {
+               if (n == 0) {
+                       return null;
+               } else if (m == null) {
+                       m = new uint[n];
+                       if (sign < 0) {
+                               Fill(0xFFFFFFFF, m);
+                       }
+                       return m;
+               }
+               int ct = m.Length;
+               if (ct < n) {
+                       uint[] r = new uint[n];
+                       Array.Copy(m, 0, r, 0, ct);
+                       return r;
+               } else if (ct == n) {
+                       return m;
+               } else {
+                       uint[] r = new uint[n];
+                       Array.Copy(m, 0, r, 0, n);
+                       if (sign < 0) {
+                               Fill(0xFFFFFFFF, r, ct, n - ct);
+                       }
+                       return r;
+               }
+       }
+
+       static void Fill(uint val, uint[] buf)
+       {
+               Fill(val, buf, 0, buf.Length);
+       }
+
+       static void Fill(uint val, uint[] buf, int off, int len)
+       {
+               while (len -- > 0) {
+                       buf[off ++] = val;
+               }
+       }
+
+       // =================================================================
+       /*
+        * Utility methods.
+        *
+        * The methods whose name begins with "Mutate" modify the array
+        * they are given as first parameter; the other methods instantiate
+        * a new array.
+        *
+        * As a rule, untrimmed arrays are accepted as input, and output
+        * may be untrimmed as well.
+        */
+
+       /*
+        * Count the number of leading zeros for a 32-bit value (number of
+        * consecutive zeros, starting with the most significant bit). This
+        * value is between 0 (for a value equal to 2^31 or greater) and
+        * 32 (for zero).
+        */
+       static int LeadingZeros(uint v)
+       {
+               if (v == 0) {
+                       return 32;
+               }
+               int n = 0;
+               if (v > 0xFFFF) { v >>= 16; } else { n += 16; }
+               if (v > 0x00FF) { v >>=  8; } else { n +=  8; }
+               if (v > 0x000F) { v >>=  4; } else { n +=  4; }
+               if (v > 0x0003) { v >>=  2; } else { n +=  2; }
+               if (v <= 0x0001) { n ++; }
+               return n;
+       }
+
+       /*
+        * Duplicate the provided magnitude array. No attempt is made at
+        * trimming. The source array MUST NOT be null.
+        */
+       static uint[] Dup(uint[] m)
+       {
+               uint[] r = new uint[m.Length];
+               Array.Copy(m, 0, r, 0, m.Length);
+               return r;
+       }
+
+       /*
+        * Increment the provided array. If there is a resulting carry,
+        * then "true" is returned, "false" otherwise. The array MUST
+        * NOT be null.
+        */
+       static bool MutateIncr(uint[] x)
+       {
+               int n = x.Length;
+               for (int i = 0; i < n; i ++) {
+                       uint w = x[i] + 1;
+                       x[i] = w;
+                       if (w != 0) {
+                               return false;
+                       }
+               }
+               return true;
+       }
+
+       /*
+        * Decrement the provided array. If there is a resulting carry,
+        * then "true" is returned, "false" otherwise. The array MUST
+        * NOT be null.
+        */
+       static bool MutateDecr(uint[] x)
+       {
+               int n = x.Length;
+               for (int i = 0; i < n; i ++) {
+                       uint w = x[i];
+                       x[i] = w - 1;
+                       if (w != 0) {
+                               return false;
+                       }
+               }
+               return true;
+       }
+
+       /*
+        * Multiply a[] with b[] (unsigned multiplication).
+        */
+       static uint[] Mul(uint[] a, uint[] b)
+       {
+               // TODO: use Karatsuba when operands are large.
+               int na = Length(0, a);
+               int nb = Length(0, b);
+               if (na == 0 || nb == 0) {
+                       return null;
+               }
+               uint[] r = new uint[na + nb];
+               for (int i = 0; i < na; i ++) {
+                       ulong ma = a[i];
+                       ulong carry = 0;
+                       for (int j = 0; j < nb; j ++) {
+                               ulong mb = (ulong)b[j];
+                               ulong mr = (ulong)r[i + j];
+                               ulong w = ma * mb + mr + carry;
+                               r[i + j] = (uint)w;
+                               carry = w >> 32;
+                       }
+                       r[i + nb] = (uint)carry;
+               }
+               return r;
+       }
+
+       /*
+        * Get the sign and magnitude of an integer. The sign is
+        * normalized to -1 (negative) or 0 (positive or zero). The
+        * magnitude is an array of length at least 1, containing the
+        * absolute value of this integer; if possible, the varray
+        * is reused (hence, the magnitude array MUST NOT be altered).
+        */
+       static void ToAbs(ZInt x, out int sign, out uint[] magn)
+       {
+               if (x.small < 0) {
+                       sign = -1;
+                       x = -x;
+               } else {
+                       sign = 0;
+               }
+               magn = x.varray;
+               if (magn == null) {
+                       magn = new uint[1] { (uint)x.small }; 
+               }
+       }
+
+       /*
+        * Compare two integers, yielding -1, 0 or 1.
+        */
+       static int Compare(int a, int b)
+       {
+               if (a < b) {
+                       return -1;
+               } else if (a == b) {
+                       return 0;
+               } else {
+                       return 1;
+               }
+       }
+
+       /*
+        * Compare a[] with b[] (unsigned). Returned value is 1 if a[]
+        * is greater than b[], 0 if they are equal, -1 otherwise.
+        */
+       static int Compare(uint[] a, uint[] b)
+       {
+               int ka = Length(0, a);
+               int kb = Length(0, b);
+               if (ka < kb) {
+                       return -1;
+               } else if (ka == kb) {
+                       while (ka > 0) {
+                               ka --;
+                               uint wa = a[ka];
+                               uint wb = b[ka];
+                               if (wa < wb) {
+                                       return -1;
+                               } else if (wa > wb) {
+                                       return 1;
+                               }
+                       }
+                       return 0;
+               } else {
+                       return 1;
+               }
+       }
+
+       /*
+        * Add b[] to a[] (unsigned). a[] is modified "in place". Only
+        * n words of a[] are modified. Moreover, the value of
+        * b[] which is added is left-shifted: words b[0]...b[n-1] are
+        * added to a[k]...a[k+n-1]. The final carry is returned ("true"
+        * for 1, "false" for 0). Neither a nor b may be null.
+        */
+       static bool MutateAdd(uint[] a, int n, uint[] b, int k)
+       {
+               bool carry = false;
+               for (int i = 0; i < n; i ++) {
+                       uint wa = a[i + k];
+                       uint wb = b[i];
+                       uint wc = wa + wb;
+                       if (carry) {
+                               wc ++;
+                               carry = wa >= wc;
+                       } else {
+                               carry = wa > wc;
+                       }
+                       a[i + k] = wc;
+               }
+               return carry;
+       }
+
+       /*
+        * Substract b[] from a[] (unsigned). a[] is modified "in
+        * place". Only n words of a[] are modified. Words
+        * b[0]...b[n-1] are subtracted from a[k]...a[k+n-1]. The final
+        * carry is returned ("true" for -1, "false" for 0). Neither a
+        * nor b may be null.
+        */
+       static bool MutateSub(uint[] a, int n, uint[] b, int k)
+       {
+               bool carry = false;
+               for (int i = 0; i < n; i ++) {
+                       uint wa = a[i + k];
+                       uint wb = b[i];
+                       uint wc = wa - wb;
+                       if (carry) {
+                               wc --;
+                               carry = wa <= wc;
+                       } else {
+                               carry = wa < wc;
+                       }
+                       a[i + k] = wc;
+               }
+               return carry;
+       }
+
+       /*
+        * Get the length (in words) of the result of a left-shift of
+        * the provided integer by k bits. If k < 0, then the value is
+        * computed for a right-shift by -k bits.
+        */
+       static int GetLengthForLeftShift(ZInt x, int k)
+       {
+               if (k < 0) {
+                       if (k == Int32.MinValue) {
+                               return 0;
+                       }
+                       return GetLengthForRightShift(x, -k);
+               }
+               uint bl = (uint)x.BitLength + (uint)k;
+               return (int)((bl + 31) >> 5);
+       }
+
+       /*
+        * Get the length (in words) of the result of a right-shift of
+        * the provided integer by k bits. If k < 0, then the value is
+        * computed for a left-shift by -k bits.
+        */
+       static int GetLengthForRightShift(ZInt x, int k)
+       {
+               if (k < 0) {
+                       if (k == Int32.MinValue) {
+                               throw new OverflowException();
+                       }
+                       return GetLengthForLeftShift(x, -k);
+               }
+               uint bl = (uint)x.BitLength;
+               if (bl <= (uint)k) {
+                       return 0;
+               } else {
+                       return (int)((bl - k + 31) >> 5);
+               }
+       }
+
+       /*
+        * Left-shift a[] (unsigned) by k bits. If k < 0, then this becomes
+        * a right-shift.
+        */
+       static uint[] ShiftLeft(uint[] a, int k)
+       {
+               if (k < 0) {
+                       return ShiftRight(a, -k);
+               } else if (k == 0) {
+                       return a;
+               }
+               int n = Length(0, a);
+               if (n == 0) {
+                       return null;
+               }
+
+               /*
+                * Allocate the result array, with the exact proper size.
+                */
+               int bl = ((n << 5) - LeadingZeros(a[n - 1])) + k;
+               uint[] r = new uint[(bl + 31) >> 5];
+
+               int kb = k & 31;
+               int kw = k >> 5;
+
+               /*
+                * Special case: shift by an integral amount of words.
+                */
+               if (kb == 0) {
+                       Array.Copy(a, 0, r, kw, n);
+                       return r;
+               }
+
+               /*
+                * Copy the bits. This loop handles one source word at
+                * a time, and writes one destination word at a time.
+                * Some unhandled bits may remain at the end.
+                */
+               uint bits = 0;
+               int zkb = 32 - kb;
+               for (int i = 0; i < n; i ++) {
+                       uint w = a[i];
+                       r[i + kw] = bits | (w << kb);
+                       bits = w >> zkb;
+               }
+               if (bits != 0) {
+                       r[n + kw] = bits;
+               }
+               return r;
+       }
+
+       /*
+        * Right-shift a[] by k bits. If k < 0, then this becomes
+        * a left-shift.
+        */
+       static uint[] ShiftRight(uint[] a, int k)
+       {
+               if (k < 0) {
+                       return ShiftLeft(a, -k);
+               } else if (k == 0) {
+                       return a;
+               }
+               int n = Length(0, a);
+               if (n == 0) {
+                       return null;
+               }
+               int bl = (n << 5) - LeadingZeros(a[n - 1]) - k;
+               if (bl <= 0) {
+                       return null;
+               }
+               uint[] r = new uint[(bl + 31) >> 5];
+
+               int kb = k & 31;
+               int kw = k >> 5;
+
+               /*
+                * Special case: shift by an integral amount of words.
+                */
+               if (kb == 0) {
+                       Array.Copy(a, kw, r, 0, r.Length);
+                       return r;
+               }
+
+               /*
+                * Copy the bits. This loop handles one source word at
+                * a time, and writes one destination word at a time.
+                * Some unhandled bits may remain at the end.
+                */
+               uint bits = a[kw] >> kb;
+               int zkb = 32 - kb;
+               for (int i = kw + 1; i < n; i ++) {
+                       uint w = a[i];
+                       r[i - kw - 1] = bits | (w << zkb);
+                       bits = w >> kb;
+               }
+               if (bits != 0) {
+                       r[n - kw - 1] = bits;
+               }
+               return r;
+       }
+
+       /*
+        * Euclidian division of a[] (unsigned) by b (single word). This
+        * method assumes that b is not 0.
+        */
+       static void DivRem(uint[] a, uint b, out uint[] q, out uint r)
+       {
+               int n = Length(0, a);
+               if (n == 0) {
+                       q = null;
+                       r = 0;
+                       return;
+               }
+               q = new uint[n];
+               ulong carry = 0;
+               for (int i = n - 1; i >= 0; i --) {
+                       /*
+                        * Performance: we strongly hope that the JIT
+                        * compiler will notice that the "/" and "%"
+                        * can be combined into a single operation.
+                        * TODO: test whether we should replace the
+                        * carry computation with:
+                        *  carry = w - (ulong)q[i] * b;
+                        */
+                       ulong w = (ulong)a[i] + (carry << 32);
+                       q[i] = (uint)(w / b);
+                       carry = w % b;
+               }
+               r = (uint)carry;
+       }
+
+       /*
+        * Euclidian division of a[] (unsigned) by b (single word). This
+        * method assumes that b is not 0. a[] is modified in place. The
+        * remainder (in the 0..b-1 range) is returned.
+        */
+       static uint MutateDivRem(uint[] a, uint b)
+       {
+               int n = Length(0, a);
+               if (n == 0) {
+                       return 0;
+               }
+               ulong carry = 0;
+               for (int i = n - 1; i >= 0; i --) {
+                       /*
+                        * Performance: we strongly hope that the JIT
+                        * compiler will notice that the "/" and "%"
+                        * can be combined into a single operation.
+                        * TODO: test whether we should replace the
+                        * carry computation with:
+                        *  carry = w - (ulong)q[i] * b;
+                        */
+                       ulong w = (ulong)a[i] + (carry << 32);
+                       a[i] = (uint)(w / b);
+                       carry = w % b;
+               }
+               return (uint)carry;
+       }
+
+       /*
+        * Euclidian division of a[] by b[] (unsigned). This method
+        * assumes that b[] is neither 0 or 1, and a[] is not smaller
+        * than b[] (this implies that the quotient won't be zero).
+        */
+       static void DivRem(uint[] a, uint[] b, out uint[] q, out uint[] r)
+       {
+               int nb = Length(0, b);
+
+               /*
+                * Special case when the divisor fits on one word.
+                */
+               if (nb == 1) {
+                       r = new uint[1];
+                       DivRem(a, b[0], out q, out r[0]);
+                       return;
+               }
+
+               /*
+                * General case.
+                *
+                * We first normalize divisor and dividend such that the
+                * most significant bit of the most significant word of
+                * the divisor is set. We can then compute the quotient
+                * word by word. In details:
+                *
+                * Let:
+                *   w = 2^32 (one word)
+                *   a = (w*a0 + a1) * w^N + a2
+                *   b = b0 * w^N + b2
+                * such that:
+                *   0 <= a0 < w
+                *   0 <= a1 < w
+                *   0 <= a2 < w^N
+                *   w/2 <= b0 < w
+                *   0 <= b2 < w^N
+                *   a < w*b
+                * In other words, a0 and a1 are the two upper words of a[],
+                * b0 is the upper word of b[] and has length 32 bits exactly,
+                * and the quotient of a by b fits in one word.
+                *
+                * Under these conditions, define q and r such that:
+                *   a = b * q + r
+                *   q >= 0
+                *   0 <= r < b
+                * We can then compute a value u this way:
+                *   if a0 = b0, then let u = w-1
+                *   otherwise, let u be such that
+                *     (w*a0 + a1) = u*b0 + v, where 0 <= v < b0
+                * It can then be shown that all these inequations hold:
+                *   0 <= u < w
+                *   u-2 <= q <= u
+                *
+                * In plain words, this allows us to compute an almost-exact
+                * estimate of the upper word of the quotient, with only
+                * one 64-bit division.
+                */
+
+               /*
+                * Normalize dividend and divisor. The normalized dividend
+                * will become the temporary array for the remainder, and
+                * we will modify it, so we make sure we have a copy.
+                */
+               int norm = LeadingZeros(b[nb - 1]);
+               r = ShiftLeft(a, norm);
+               if (r == a) {
+                       r = new uint[a.Length];
+                       Array.Copy(a, 0, r, 0, a.Length);
+               }
+               uint[] b2 = ShiftLeft(b, norm);
+               int nr = Length(0, r);
+#if DEBUG
+               if (Length(0, b2) != nb) {
+                       throw new Exception("normalize error 1");
+               }
+               if (b2[nb - 1] < 0x80000000) {
+                       throw new Exception("normalize error 2");
+               }
+               {
+                       uint[] ta = ShiftRight(r, norm);
+                       if (Compare(a, ta) != 0) {
+                               throw new Exception("normalize error 3");
+                       }
+                       uint[] tb = ShiftRight(b2, norm);
+                       if (Compare(b, tb) != 0) {
+                               throw new Exception("normalize error 4");
+                       }
+               }
+#endif
+               b = b2;
+
+               /*
+                * Length of the quotient will be (at most) k words. This
+                * is the number of iterations in the loop below.
+                */
+               int k = (nr - nb) + 1;
+#if DEBUG
+               if (k <= 0) {
+                       throw new Exception("wrong iteration count: " + k);
+               }
+#endif
+               q = new uint[k];
+
+               /*
+                * The upper word of a[] (the one we modify, i.e. currently
+                * stored in r[]) is in a0; it is carried over from the
+                * previous loop iteration. Initially, it is zero.
+                */
+               uint a0 = 0;
+               uint b0 = b[nb - 1];
+               int j = nr;
+               while (k -- > 0) {
+                       uint a1 = r[-- j];
+                       uint u;
+                       if (a0 == b0) {
+                               u = 0xFFFFFFFF;
+                       } else {
+                               ulong ah = ((ulong)a0 << 32) | (ulong)a1;
+                               u = (uint)(ah / b0);
+                       }
+
+                       /*
+                        * Candidate word for the quotient:
+                        * -- if u = 0 then qw is necessarily 0, and the
+                        *    rest of this iteration is trivial;
+                        * -- if u = 1 then we try qw = 1;
+                        * -- otherwise, we try qw = u-1.
+                        */
+                       uint qw;
+                       if (u == 0) {
+                               q[k] = 0;
+                               a0 = a1;
+                               continue;
+                       } else if (u == 1) {
+                               qw = 1;
+                       } else {
+                               qw = u - 1;
+                       }
+
+                       /*
+                        * "Trying" a candidate word means subtracting from
+                        * r[] the product qw*b (b[] being shifted by k words).
+                        * The result may be negative, in which case we
+                        * overestimated qw; it may be greater than b or
+                        * equal to b, in which case we underestimated qw;
+                        * or it may be just fine.
+                        */
+                       ulong carry = 0;
+                       bool tooBig = true;
+                       for (int i = 0; i < nb; i ++) {
+                               uint wb = b[i];
+                               ulong z = (ulong)wb * (ulong)qw + carry;
+                               carry = z >> 32;
+                               uint wa = r[i + k];
+                               uint wc = wa - (uint)z;
+                               if (wc > wa) {
+                                       carry ++;
+                               }
+                               r[i + k] = wc;
+                               if (wc != wb) {
+                                       tooBig = wc > wb;
+                               }
+                       }
+
+                       /*
+                        * Once we have adjusted everything, the upper word
+                        * of r[] will be nullified; wo do it now. Note that
+                        * for the first loop iteration, that upper word
+                        * may be absent (so already zero, but also "virtual").
+                        */
+                       if (nb + k < nr) {
+                               r[nb + k] = 0;
+                       }
+
+                       /*
+                        * At that point, "carry" should be equal to a0
+                        * if we estimated right.
+                        */
+                       if (carry < (ulong)a0) {
+                               /*
+                                * Underestimate.
+                                */
+                               qw ++;
+#if DEBUG
+                               if (carry + 1 != (ulong)a0) {
+                                       throw new Exception("div error 1");
+                               }
+                               if (!MutateSub(r, nb, b, k)) {
+                                       throw new Exception("div error 2");
+                               }
+#else
+                               MutateSub(r, nb, b, k);
+#endif
+                       } else if (carry > (ulong)a0) {
+                               /*
+                                * Overestimate.
+                                */
+                               qw --;
+#if DEBUG
+                               if (carry - 1 != (ulong)a0) {
+                                       throw new Exception("div error 3");
+                               }
+                               if (!MutateAdd(r, nb, b, k)) {
+                                       throw new Exception("div error 4");
+                               }
+#else
+                               MutateAdd(r, nb, b, k);
+#endif
+                       } else if (tooBig) {
+                               /*
+                                * Underestimate, but no expected carry.
+                                */
+                               qw ++;
+#if DEBUG
+                               if (MutateSub(r, nb, b, k)) {
+                                       throw new Exception("div error 5");
+                               }
+#else
+                               MutateSub(r, nb, b, k);
+#endif
+                       }
+
+                       q[k] = qw;
+                       a0 = r[j];
+               }
+
+               /*
+                * At that point, r[] contains the remainder but needs
+                * to be shifted back, to account for the normalization
+                * performed before. q[] is correct (but possibly
+                * untrimmed).
+                */
+               r = ShiftRight(r, norm);
+       }
+
+       // =================================================================
+
+       /*
+        * Conversion to sbyte; an OverflowException is thrown if the
+        * value does not fit. Use ToInt to get a truncating conversion.
+        */
+       public static explicit operator sbyte(ZInt val)
+       {
+               int x = (int)val;
+               if (x < SByte.MinValue || x > SByte.MaxValue) {
+                       throw new OverflowException();
+               }
+               return (sbyte)x;
+       }
+
+       /*
+        * Conversion to byte; an OverflowException is thrown if the
+        * value does not fit. Use ToInt to get a truncating conversion.
+        */
+       public static explicit operator byte(ZInt val)
+       {
+               int x = (int)val;
+               if (x > Byte.MaxValue) {
+                       throw new OverflowException();
+               }
+               return (byte)x;
+       }
+
+       /*
+        * Conversion to short; an OverflowException is thrown if the
+        * value does not fit. Use ToInt to get a truncating conversion.
+        */
+       public static explicit operator short(ZInt val)
+       {
+               int x = (int)val;
+               if (x < Int16.MinValue || x > Int16.MaxValue) {
+                       throw new OverflowException();
+               }
+               return (short)x;
+       }
+
+       /*
+        * Conversion to ushort; an OverflowException is thrown if the
+        * value does not fit. Use ToInt to get a truncating conversion.
+        */
+       public static explicit operator ushort(ZInt val)
+       {
+               int x = (int)val;
+               if (x > UInt16.MaxValue) {
+                       throw new OverflowException();
+               }
+               return (ushort)x;
+       }
+
+       /*
+        * Conversion to int; an OverflowException is thrown if the
+        * value does not fit. Use ToInt to get a truncating conversion.
+        */
+       public static explicit operator int(ZInt val)
+       {
+               if (val.varray != null) {
+                       throw new OverflowException();
+               }
+               return val.small;
+       }
+
+       /*
+        * Conversion to uint; an OverflowException is thrown if the
+        * value does not fit. Use ToUInt to get a truncating conversion.
+        */
+       public static explicit operator uint(ZInt val)
+       {
+               int s = val.small;
+               if (s < 0) {
+                       throw new OverflowException();
+               }
+               uint[] m = val.varray;
+               if (m == null) {
+                       return (uint)s;
+               } else if (m.Length > 1) {
+                       throw new OverflowException();
+               } else {
+                       return m[0];
+               }
+       }
+
+       /*
+        * Conversion to long; an OverflowException is thrown if the
+        * value does not fit. Use ToLong to get a truncating conversion.
+        */
+       public static explicit operator long(ZInt val)
+       {
+               int s = val.small;
+               uint[] m = val.varray;
+               if (m == null) {
+                       return (long)s;
+               } else if (m.Length == 1) {
+                       return (long)m[0] | ((long)s << 32);
+               } else if (m.Length == 2) {
+                       uint w0 = m[0];
+                       uint w1 = m[1];
+                       if (((w1 ^ (uint)s) >> 31) != 0) {
+                               throw new OverflowException();
+                       }
+                       return (long)w0 | ((long)w1 << 32);
+               } else {
+                       throw new OverflowException();
+               }
+       }
+
+       /*
+        * Conversion to ulong; an OverflowException is thrown if the
+        * value does not fit. Use ToULong to get a truncating conversion.
+        */
+       public static explicit operator ulong(ZInt val)
+       {
+               int s = val.small;
+               if (s < 0) {
+                       throw new OverflowException();
+               }
+               uint[] m = val.varray;
+               if (m == null) {
+                       return (ulong)s;
+               } else if (m.Length == 1) {
+                       return (ulong)m[0];
+               } else if (m.Length == 2) {
+                       return (ulong)m[0] | ((ulong)m[1] << 32);
+               } else {
+                       throw new OverflowException();
+               }
+       }
+
+       /*
+        * By definition, conversion from sbyte conserves the value.
+        */
+       public static implicit operator ZInt(sbyte val)
+       {
+               return new ZInt((int)val);
+       }
+
+       /*
+        * By definition, conversion from byte conserves the value.
+        */
+       public static implicit operator ZInt(byte val)
+       {
+               return new ZInt((uint)val);
+       }
+
+       /*
+        * By definition, conversion from short conserves the value.
+        */
+       public static implicit operator ZInt(short val)
+       {
+               return new ZInt((int)val);
+       }
+
+       /*
+        * By definition, conversion from ushort conserves the value.
+        */
+       public static implicit operator ZInt(ushort val)
+       {
+               return new ZInt((uint)val);
+       }
+
+       /*
+        * By definition, conversion from int conserves the value.
+        */
+       public static implicit operator ZInt(int val)
+       {
+               return new ZInt(val);
+       }
+
+       /*
+        * By definition, conversion from uint conserves the value.
+        */
+       public static implicit operator ZInt(uint val)
+       {
+               return new ZInt(val);
+       }
+
+       /*
+        * By definition, conversion from long conserves the value.
+        */
+       public static implicit operator ZInt(long val)
+       {
+               return new ZInt(val);
+       }
+
+       /*
+        * By definition, conversion from ulong conserves the value.
+        */
+       public static implicit operator ZInt(ulong val)
+       {
+               return new ZInt(val);
+       }
+
+       /*
+        * Unary '+' operator is a no-operation.
+        */
+       public static ZInt operator +(ZInt a)
+       {
+               return a;
+       }
+
+       /*
+        * Unary negation.
+        */
+       public static ZInt operator -(ZInt a)
+       {
+               int s = a.small;
+               uint[] m = a.varray;
+               if (m == null) {
+                       if (s == Int32.MinValue) {
+                               return new ZInt(0, new uint[1] { 0x80000000 });
+                       } else {
+                               return new ZInt(-s, null);
+                       }
+               }
+
+               /*
+                * Two's complement: invert all bits, then add 1. The
+                * result array will usually have the same size, but may
+                * be one word longer or one word shorter.
+                */
+               int n = Length(s, m);
+               uint[] bm = new uint[n];
+               for (int i = 0; i < n; i ++) {
+                       bm[i] = ~m[i];
+               }
+               if (MutateIncr(bm)) {
+                       /*
+                        * Extra carry. This may happen only if the source
+                        * array contained only zeros, which may happen only
+                        * for a source value -2^(32*k) for some integer k
+                        * (k > 0).
+                        */
+                       bm = new uint[n + 1];
+                       bm[n] = 1;
+                       return new ZInt(0, bm);
+               } else {
+                       /*
+                        * The resulting array might be too big by one word,
+                        * so we must not assume that it is trimmed.
+                        */
+                       return Make((int)~(uint)s, bm);
+               }
+       }
+
+       /*
+        * Addition operator.
+        */
+       public static ZInt operator +(ZInt a, ZInt b)
+       {
+               int sa = a.small;
+               int sb = b.small;
+               uint[] ma = a.varray;
+               uint[] mb = b.varray;
+               if (ma == null) {
+                       if (sa == 0) {
+                               return b;
+                       }
+                       if (mb == null) {
+                               if (sb == 0) {
+                                       return a;
+                               }
+                               return new ZInt((long)sa + (long)sb);
+                       }
+                       ma = new uint[1] { (uint)sa };
+                       sa >>= 31;
+               } else if (mb == null) {
+                       if (sb == 0) {
+                               return a;
+                       }
+                       mb = new uint[1] { (uint)sb };
+                       sb >>= 31;
+               }
+               int na = ma.Length;
+               int nb = mb.Length;
+               int n = Math.Max(na, nb) + 1;
+               uint[] mc = new uint[n];
+               ulong carry = 0;
+               for (int i = 0; i < n; i ++) {
+                       uint wa = i < na ? ma[i] : (uint)sa;
+                       uint wb = i < nb ? mb[i] : (uint)sb;
+                       ulong z = (ulong)wa + (ulong)wb + carry;
+                       mc[i] = (uint)z;
+                       carry = z >> 32;
+               }
+               return Make((-(int)carry) ^ sa ^ sb, mc);
+       }
+
+       /*
+        * Subtraction operator.
+        */
+       public static ZInt operator -(ZInt a, ZInt b)
+       {
+               int sa = a.small;
+               int sb = b.small;
+               uint[] ma = a.varray;
+               uint[] mb = b.varray;
+               if (ma == null) {
+                       if (sa == 0) {
+                               return -b;
+                       }
+                       if (mb == null) {
+                               if (sb == 0) {
+                                       return a;
+                               }
+                               return new ZInt((long)sa - (long)sb);
+                       }
+                       ma = new uint[1] { (uint)sa };
+                       sa >>= 31;
+               } else if (mb == null) {
+                       if (sb == 0) {
+                               return a;
+                       }
+                       mb = new uint[1] { (uint)sb };
+                       sb >>= 31;
+               }
+               int na = ma.Length;
+               int nb = mb.Length;
+               int n = Math.Max(na, nb) + 1;
+               uint[] mc = new uint[n];
+               long carry = 0;
+               for (int i = 0; i < n; i ++) {
+                       uint wa = i < na ? ma[i] : (uint)sa;
+                       uint wb = i < nb ? mb[i] : (uint)sb;
+                       long z = (long)wa - (long)wb + carry;
+                       mc[i] = (uint)z;
+                       carry = z >> 32;
+               }
+               return Make((int)carry ^ sa ^ sb, mc);
+       }
+
+       /*
+        * Increment operator.
+        */
+       public static ZInt operator ++(ZInt a)
+       {
+               int s = a.small;
+               uint[] ma = a.varray;
+               if (ma == null) {
+                       return new ZInt((long)s + 1);
+               }
+               uint[] mb = Dup(ma);
+               if (MutateIncr(mb)) {
+                       int n = ma.Length;
+                       mb = new uint[n + 1];
+                       mb[n] = 1;
+                       return new ZInt(0, mb);
+               } else {
+                       return Make(s, mb);
+               }
+       }
+
+       /*
+        * Decrement operator.
+        */
+       public static ZInt operator --(ZInt a)
+       {
+               int s = a.small;
+               uint[] ma = a.varray;
+               if (ma == null) {
+                       return new ZInt((long)s - 1);
+               }
+
+               /*
+                * MutateDecr() will report a carry only if the varray
+                * contained only zeros; since this value was not small,
+                * then it must have been negative.
+                */
+               uint[] mb = Dup(ma);
+               if (MutateDecr(mb)) {
+                       int n = mb.Length;
+                       uint[] mc = new uint[n + 1];
+                       Array.Copy(mb, 0, mc, 0, n);
+                       mc[n] = 0xFFFFFFFE;
+                       return new ZInt(-1, mc);
+               } else {
+                       return Make(s, mb);
+               }
+       }
+
+       /*
+        * Multiplication operator.
+        */
+       public static ZInt operator *(ZInt a, ZInt b)
+       {
+               int sa = a.small;
+               int sb = b.small;
+               uint[] ma = a.varray;
+               uint[] mb = b.varray;
+
+               /*
+                * Special cases:
+                * -- one of the operands is zero
+                * -- both operands are small
+                */
+               if (ma == null) {
+                       if (sa == 0) {
+                               return Zero;
+                       }
+                       if (mb == null) {
+                               if (sb == 0) {
+                                       return Zero;
+                               }
+                               return new ZInt((long)sa * (long)sb);
+                       }
+               } else if (mb == null) {
+                       if (sb == 0) {
+                               return Zero;
+                       }
+               }
+
+               /*
+                * Get both values in sign+magnitude representation.
+                */
+               ToAbs(a, out sa, out ma);
+               ToAbs(b, out sb, out mb);
+
+               /*
+                * Compute the product. Set the sign.
+                */
+               ZInt r = Make(0, Mul(ma, mb));
+               if ((sa ^ sb) < 0) {
+                       r = -r;
+               }
+               return r;
+       }
+
+       /*
+        * Integer division: the quotient is returned, and the remainder
+        * is written in 'r'.
+        *
+        * This operation follows the C# rules for integer division:
+        * -- rounding is towards 0
+        * -- quotient is positive if dividend and divisor have the same
+        *    sign, negative otherwise
+        * -- remainder has the sign of the dividend
+        *
+        * Attempt at dividing by zero triggers a DivideByZeroException.
+        */
+       public static ZInt DivRem(ZInt a, ZInt b, out ZInt r)
+       {
+               ZInt q;
+               DivRem(a, b, out q, out r);
+               return q;
+       }
+
+       static void DivRem(ZInt a, ZInt b,
+               out ZInt q, out ZInt r)
+       {
+               int sa = a.small;
+               int sb = b.small;
+               uint[] ma = a.varray;
+               uint[] mb = b.varray;
+
+               /*
+                * Division by zero triggers an exception.
+                */
+               if (mb == null && sb == 0) {
+                       throw new DivideByZeroException();
+               }
+
+               /*
+                * If the dividend is zero, then both quotient and
+                * remainder are zero.
+                */
+               if (ma == null && sa == 0) {
+                       q = Zero;
+                       r = Zero;
+                       return;
+               }
+
+               /*
+                * If both dividend and divisor are small, then we
+                * use the native 64-bit integer types. If only the
+                * divisor is small, then we have a special fast case
+                * for division by 1 or -1.
+                */
+               if (ma == null && mb == null) {
+                       q = new ZInt((long)sa / (long)sb);
+                       r = new ZInt((long)sa % (long)sb);
+                       return;
+               }
+               if (mb == null) {
+                       if (sb == 1) {
+                               q = a;
+                               r = Zero;
+                               return;
+                       } else if (sb == -1) {
+                               q = -a;
+                               r = Zero;
+                               return;
+                       }
+               }
+
+               /*
+                * We know that the dividend is not 0, and the divisor
+                * is not -1, 0 or 1. We now want the sign+magnitude
+                * representations of both operands.
+                */
+               ToAbs(a, out sa, out ma);
+               ToAbs(b, out sb, out mb);
+
+               /*
+                * If the divisor is greater (in magnitude) than the
+                * dividend, then the quotient is zero and the remainder
+                * is equal to the dividend. If the divisor and dividend
+                * are equal in magnitude, then the remainder is zero and
+                * the quotient is 1 if divisor and dividend have the same
+                * sign, -1 otherwise.
+                */
+               int cc = Compare(ma, mb);
+               if (cc < 0) {
+                       q = Zero;
+                       r = a;
+                       return;
+               } else if (cc == 0) {
+                       q = (sa == sb) ? One : MinusOne;
+                       r = Zero;
+                       return;
+               }
+
+               /*
+                * At that point, we know that the divisor is not -1, 0
+                * or 1, and that the quotient will not be 0. We perform
+                * the unsigned division (with the magnitudes), then
+                * we adjust the signs.
+                */
+
+               uint[] mq, mr;
+               DivRem(ma, mb, out mq, out mr);
+
+               /*
+                * Quotient is positive if divisor and dividend have the
+                * same sign, negative otherwise. Remainder always has
+                * the sign of the dividend, but it may be zero.
+                */
+               q = Make(0, mq);
+               if (sa != sb) {
+                       q = -q;
+               }
+               r = Make(0, mr);
+               if (sa < 0) {
+                       r = -r;
+               }
+#if DEBUG
+               if (q * b + r != a) {
+                       throw new Exception("division error");
+               }
+#endif
+       }
+
+       /*
+        * Division operator: see DivRem() for details.
+        */
+       public static ZInt operator /(ZInt a, ZInt b)
+       {
+               ZInt q, r;
+               DivRem(a, b, out q, out r);
+               return q;
+       }
+
+       /*
+        * Remainder operator: see DivRem() for details.
+        */
+       public static ZInt operator %(ZInt a, ZInt b)
+       {
+               ZInt q, r;
+               DivRem(a, b, out q, out r);
+               return r;
+       }
+
+       /*
+        * Reduce this value modulo the provided m. This differs from
+        * '%' in that the returned value is always in the 0 to abs(m)-1
+        * range.
+        */
+       public ZInt Mod(ZInt m)
+       {
+               ZInt r = this % m;
+               if (r.small < 0) {
+                       if (m.small < 0) {
+                               m = -m;
+                       }
+                       r += m;
+               }
+               return r;
+       }
+
+       /*
+        * Left-shift operator.
+        */
+       public static ZInt operator <<(ZInt a, int n)
+       {
+               if (n < 0) {
+                       if (n == Int32.MinValue) {
+                               return Zero;
+                       }
+                       return a >> (-n);
+               }
+               int sa = a.small;
+               uint[] ma = a.varray;
+               if (ma == null) {
+                       if (n <= 32) {
+                               return new ZInt((long)sa << n);
+                       }
+                       if (sa == 0) {
+                               return Zero;
+                       }
+               }
+               uint[] mr = new uint[GetLengthForLeftShift(a, n)];
+
+               /*
+                * Special case when the shift is a multiple of 32.
+                */
+               int kw = n >> 5;
+               int kb = n & 31;
+               if (kb == 0) {
+                       if (ma == null) {
+                               mr[kw] = (uint)sa;
+                               return new ZInt(sa >> 31, mr);
+                       } else {
+                               Array.Copy(ma, 0, mr, kw, ma.Length);
+                               return new ZInt(sa, mr);
+                       }
+               }
+
+               /*
+                * At that point, we know that the source integer does
+                * not fit in a signed "int", or is shifted by more than
+                * 32 bits, or both. Either way, the result will not fit
+                * in an "int".
+                *
+                * We process all input words one by one.
+                */
+               uint rem = 0;
+               int ikb = 32 - kb;
+               int j;
+               if (ma == null) {
+                       j = 1;
+                       uint wa = (uint)sa;
+                       mr[kw] = wa << kb;
+                       rem = wa >> ikb;
+               } else {
+                       j = ma.Length;
+                       for (int i = 0; i < j; i ++) {
+                               uint wa = ma[i];
+                               mr[i + kw] = rem | (wa << kb);
+                               rem = wa >> ikb;
+                       }
+               }
+               sa >>= 31;
+#if DEBUG
+               if ((j + kw) == mr.Length - 1) {
+                       if (rem == ((uint)sa >> ikb)) {
+                               throw new Exception(
+                                       "Wrong left-shift: untrimmed");
+                       }
+               } else if ((j + kw) == mr.Length) {
+                       if (rem != ((uint)sa >> ikb)) {
+                               throw new Exception(
+                                       "Wrong left-shift: dropped bits");
+                       }
+               } else {
+                       throw new Exception(
+                               "Wrong left-shift: oversized output length");
+               }
+#endif
+               if ((j + kw) < mr.Length) {
+                       mr[j + kw] = rem | ((uint)sa << kb);
+               }
+               return new ZInt(sa, mr);
+       }
+
+       /*
+        * Right-shift operator.
+        */
+       public static ZInt operator >>(ZInt a, int n)
+       {
+               if (n < 0) {
+                       if (n == Int32.MinValue) {
+                               throw new OverflowException();
+                       }
+                       return a << (-n);
+               }
+               int sa = a.small;
+               uint[] ma = a.varray;
+               if (ma == null) {
+                       /*
+                        * If right-shifting a "small" value, then we can
+                        * do the computation with the native ">>" operator
+                        * on "int" values, unless the shift count is 32
+                        * or more, in which case we get either 0 or -1,
+                        * depending on the source value sign.
+                        */
+                       if (n < 32) {
+                               return new ZInt(sa >> n, null);
+                       } else {
+                               return new ZInt(sa >> 31, null);
+                       }
+               }
+
+               /*
+                * At that point, we know that the source value uses
+                * a non-null varray. We compute the bit length of the
+                * result. If the result would fit in an "int" (bit length
+                * of 31 or less) then we handle it as a special case.
+                */
+               int kw = n >> 5;
+               int kb = n & 31;
+               int bl = a.BitLength - n;
+               if (bl <= 0) {
+                       return new ZInt(sa, null);
+               } else if (bl <= 31) {
+                       if (kb == 0) {
+                               return new ZInt((int)ma[kw], null);
+                       } else {
+                               int p = ma.Length;
+                               uint w0 = ma[kw];
+                               uint w1 = (kw + 1) < p ? ma[kw + 1] : (uint)sa;
+                               return new ZInt((int)((w0 >> kb)
+                                       | (w1 << (32 - kb))), null);
+                       }
+               }
+
+               /*
+                * Result will require an array. Let's allocate it.
+                */
+               uint[] mr = new uint[(bl + 31) >> 5];
+
+               /*
+                * Special case when the shift is a multiple of 32.
+                */
+               if (kb == 0) {
+#if DEBUG
+                       if (mr.Length != (ma.Length - kw)) {
+                               throw new Exception(
+                                       "Wrong right-shift: output length");
+                       }
+#endif
+                       Array.Copy(ma, kw, mr, 0, ma.Length - kw);
+                       return new ZInt(sa, mr);
+               }
+
+               /*
+                * We process all input words one by one.
+                */
+               int ikb = 32 - kb;
+               uint rem = ma[kw] >> kb;
+               int j = ma.Length;
+               for (int i = kw + 1; i < j; i ++) {
+                       uint wa = ma[i];
+                       mr[i - kw - 1] = rem | (wa << ikb);
+                       rem = wa >> kb;
+               }
+#if DEBUG
+               if ((j - kw - 1) == mr.Length - 1) {
+                       if (rem == ((uint)sa >> kb)) {
+                               throw new Exception(
+                                       "Wrong right-shift: untrimmed");
+                       }
+               } else if ((j - kw - 1) == mr.Length) {
+                       if (rem != ((uint)sa >> kb)) {
+                               throw new Exception(
+                                       "Wrong right-shift: dropped bits");
+                       }
+               } else {
+                       throw new Exception(
+                               "Wrong right-shift: oversized output length");
+               }
+#endif
+               if ((j - kw - 1) < mr.Length) {
+                       mr[j - kw - 1] = rem | ((uint)sa << ikb);
+               }
+               return new ZInt(sa, mr);
+       }
+
+       /*
+        * NOTES ON BITWISE BOOLEAN OPERATIONS
+        *
+        * When both operands are "small" then the result is necessarily
+        * small: in "small" encoding, all bits beyond bit 31 of the
+        * two's complement encoding are equal to bit 31, so the result
+        * of computing the operation on bits 31 will also be valid for
+        * all subsequent bits. Therefore, when the two operands are
+        * small, we can just do the operation on the "int" values and the
+        * result is guaranteed "small" as well.
+        */
+
+       /*
+        * Compute the bitwise AND between a "big" and a "small" values.
+        */
+       static ZInt AndSmall(int s, uint[] m, int x)
+       {
+               if (x < 0) {
+                       uint[] r = Dup(m);
+                       r[0] &= (uint)x;
+                       return new ZInt(s, r);
+               } else {
+                       return new ZInt(x & (int)m[0], null);
+               }
+       }
+
+       /*
+        * Bitwise AND operator.
+        */
+       public static ZInt operator &(ZInt a, ZInt b)
+       {
+               int sa = a.small;
+               int sb = b.small;
+               uint[] ma = a.varray;
+               uint[] mb = b.varray;
+               if (ma == null) {
+                       if (mb == null) {
+                               return new ZInt(sa & sb, null);
+                       } else {
+                               return AndSmall(sb, mb, sa);
+                       }
+               } else if (mb == null) {
+                       return AndSmall(sa, ma, sb);
+               }
+
+               /*
+                * Both values are big. Since upper zero bits force the
+                * result to contain zeros, the result size is that of the
+                * positive operand (the smallest of the two if both are
+                * positive). If both operands are negative, then the
+                * result magnitude may be as large as the largest of the
+                * two source magnitudes.
+                *
+                * Result is negative if and only if both operands are
+                * negative.
+                */
+               int na = ma.Length;
+               int nb = mb.Length;
+               int nr;
+               if (sa >= 0) {
+                       if (sb >= 0) {
+                               nr = Math.Min(na, nb);
+                       } else {
+                               nr = na;
+                       }
+               } else {
+                       if (sb >= 0) {
+                               nr = nb;
+                       } else {
+                               nr = Math.Max(na, nb);
+                       }
+               }
+               uint[] mr = new uint[nr];
+               for (int i = 0; i < nr; i ++) {
+                       uint wa = i < na ? ma[i] : (uint)sa;
+                       uint wb = i < nb ? mb[i] : (uint)sb;
+                       mr[i] = wa & wb;
+               }
+               return Make(sa & sb, mr);
+       }
+
+       /*
+        * Compute the bitwise OR between a "big" value (sign and
+        * magnitude, already normalized/trimmed), and a small value
+        * (which can be positive or negative).
+        */
+       static ZInt OrSmall(int s, uint[] m, int x)
+       {
+               if (x < 0) {
+                       return new ZInt(x | (int)m[0], null);
+               } else {
+                       uint[] r = Dup(m);
+                       r[0] |= (uint)x;
+                       return new ZInt(s, r);
+               }
+       }
+
+       /*
+        * Bitwise OR operator.
+        */
+       public static ZInt operator |(ZInt a, ZInt b)
+       {
+               int sa = a.small;
+               int sb = b.small;
+               uint[] ma = a.varray;
+               uint[] mb = b.varray;
+               if (ma == null) {
+                       if (mb == null) {
+                               return new ZInt(sa | sb, null);
+                       } else {
+                               return OrSmall(sb, mb, sa);
+                       }
+               } else if (mb == null) {
+                       return OrSmall(sa, ma, sb);
+               }
+
+               /*
+                * Both values are big. Since upper one bits force the
+                * result to contain ones, the result size is that of
+                * the negative operand (the greater, i.e. "smallest" of
+                * the two if both are negative). If both operands are
+                * positive, then the result magnitude may be as large
+                * as the largest of the two source magnitudes.
+                *
+                * Result is positive if and only if both operands are
+                * positive.
+                */
+               int na = ma.Length;
+               int nb = mb.Length;
+               int nr;
+               if (sa >= 0) {
+                       if (sb >= 0) {
+                               nr = Math.Max(na, nb);
+                       } else {
+                               nr = nb;
+                       }
+               } else {
+                       if (sb >= 0) {
+                               nr = na;
+                       } else {
+                               nr = Math.Min(na, nb);
+                       }
+               }
+               uint[] mr = new uint[nr];
+               for (int i = 0; i < nr; i ++) {
+                       uint wa = i < na ? ma[i] : (uint)sa;
+                       uint wb = i < nb ? mb[i] : (uint)sb;
+                       mr[i] = wa | wb;
+               }
+               return Make(sa | sb, mr);
+       }
+
+       /*
+        * Bitwise XOR operator.
+        */
+       public static ZInt operator ^(ZInt a, ZInt b)
+       {
+               int sa = a.small;
+               int sb = b.small;
+               uint[] ma = a.varray;
+               uint[] mb = b.varray;
+               if (ma == null && mb == null) {
+                       return new ZInt(sa ^ sb, null);
+               }
+               if (ma == null) {
+                       int st = sa;
+                       sa = sb;
+                       sb = st;
+                       uint[] mt = ma;
+                       ma = mb;
+                       mb = mt;
+               }
+               if (mb == null) {
+                       int nx = ma.Length;
+                       uint[] mx = new uint[nx];
+                       mx[0] = ma[0] ^ (uint)sb;
+                       if (nx > 1) {
+                               if (sb < 0) {
+                                       for (int i = 1; i < nx; i ++) {
+                                               mx[i] = ~ma[i];
+                                       }
+                               } else {
+                                       Array.Copy(ma, 1, mx, 1, nx - 1);
+                               }
+                       }
+                       return Make(sa ^ (sb >> 31), mx);
+               }
+
+               /*
+                * Both operands use varrays.
+                * Result can be as big as the bigger of the two operands
+                * (it _will_ be that big, necessarily, if the two operands
+                * have distinct sizes).
+                */
+               int na = ma.Length;
+               int nb = mb.Length;
+               int nr = Math.Max(na, nb);
+               uint[] mr = new uint[nr];
+               for (int i = 0; i < nr; i ++) {
+                       uint wa = i < na ? ma[i] : (uint)sa;
+                       uint wb = i < nb ? mb[i] : (uint)sb;
+                       mr[i] = wa ^ wb;
+               }
+               return Make((sa ^ sb) >> 31, mr);
+       }
+
+       /*
+        * Bitwise inversion operator.
+        */
+       public static ZInt operator ~(ZInt a)
+       {
+               int s = a.small;
+               uint[] m = a.varray;
+               if (m == null) {
+                       return new ZInt(~s, null);
+               } else {
+                       int n = m.Length;
+                       uint[] r = new uint[n];
+                       for (int i = 0; i < n; i ++) {
+                               r[i] = ~m[i];
+                       }
+                       return new ZInt(~s, r);
+               }
+       }
+
+       /*
+        * Basic comparison; returned value is -1, 0 or 1 depending on
+        * whether this instance is to be considered lower then, equal
+        * to, or greater than the provided object.
+        *
+        * All ZInt instances are considered greater than 'null', and
+        * lower than any non-null object that is not a ZInt.
+        */
+       public int CompareTo(object obj)
+       {
+               if (obj == null) {
+                       return 1;
+               }
+               if (!(obj is ZInt)) {
+                       return -1;
+               }
+               return CompareTo((ZInt)obj);
+       }
+
+       /*
+        * Basic comparison; returned value is -1, 0 or 1 depending on
+        * whether this instance is to be considered lower then, equal
+        * to, or greater than the provided value.
+        */
+       public int CompareTo(ZInt v)
+       {
+               int sv = v.small;
+               uint[] mv = v.varray;
+               int sign1 = small >> 31;
+               int sign2 = sv >> 31;
+               if (sign1 != sign2) {
+                       /*
+                        * One of the sign* values is -1, the other is 0.
+                        */
+                       return sign1 - sign2;
+               }
+
+               /*
+                * Both values have the same sign. Since the varrays are
+                * trimmed, we can use their presence and length to
+                * quickly resolve most cases.
+                */
+               if (small < 0) {
+                       if (varray == null) {
+                               if (mv == null) {
+                                       return Compare(small, sv);
+                               } else {
+                                       return 1;
+                               }
+                       } else {
+                               if (mv == null) {
+                                       return -1;
+                               } else {
+                                       int n1 = varray.Length;
+                                       int n2 = mv.Length;
+                                       if (n1 < n2) {
+                                               return 1;
+                                       } else if (n1 == n2) {
+                                               return Compare(varray, mv);
+                                       } else {
+                                               return -1;
+                                       }
+                               }
+                       }
+               } else {
+                       if (varray == null) {
+                               if (mv == null) {
+                                       return Compare(small, sv);
+                               } else {
+                                       return -1;
+                               }
+                       } else {
+                               if (mv == null) {
+                                       return 1;
+                               } else {
+                                       return Compare(varray, mv);
+                               }
+                       }
+               }
+       }
+
+       /*
+        * Equality comparison: a ZInt instance is equal only to another
+        * ZInt instance that encodes the same integer value.
+        */
+       public override bool Equals(object obj)
+       {
+               if (obj == null) {
+                       return false;
+               }
+               if (!(obj is ZInt)) {
+                       return false;
+               }
+               return CompareTo((ZInt)obj) == 0;
+       }
+
+       /*
+        * Equality comparison: a ZInt instance is equal only to another
+        * ZInt instance that encodes the same integer value.
+        */
+       public bool Equals(ZInt v)
+       {
+               return CompareTo(v) == 0;
+       }
+
+       /*
+        * The hash code for a ZInt is equal to its lower 32 bits.
+        */
+       public override int GetHashCode()
+       {
+               if (varray == null) {
+                       return small;
+               } else {
+                       return (int)varray[0];
+               }
+       }
+
+       /*
+        * Equality operator.
+        */
+       public static bool operator ==(ZInt a, ZInt b)
+       {
+               return a.CompareTo(b) == 0;
+       }
+
+       /*
+        * Inequality operator.
+        */
+       public static bool operator !=(ZInt a, ZInt b)
+       {
+               return a.CompareTo(b) != 0;
+       }
+
+       /*
+        * Lower-than operator.
+        */
+       public static bool operator <(ZInt a, ZInt b)
+       {
+               return a.CompareTo(b) < 0;
+       }
+
+       /*
+        * Lower-or-equal operator.
+        */
+       public static bool operator <=(ZInt a, ZInt b)
+       {
+               return a.CompareTo(b) <= 0;
+       }
+
+       /*
+        * Greater-than operator.
+        */
+       public static bool operator >(ZInt a, ZInt b)
+       {
+               return a.CompareTo(b) > 0;
+       }
+
+       /*
+        * Greater-or-equal operator.
+        */
+       public static bool operator >=(ZInt a, ZInt b)
+       {
+               return a.CompareTo(b) >= 0;
+       }
+
+       /*
+        * Power function: this raises x to the power e. The exponent e
+        * MUST NOT be negative. If x and e are both zero, then 1 is
+        * returned.
+        */
+       public static ZInt Pow(ZInt x, int e)
+       {
+               if (e < 0) {
+                       throw new ArgumentOutOfRangeException();
+               }
+               if (e == 0) {
+                       return One;
+               }
+               if (e == 1 || x.IsZero || x.IsOne) {
+                       return x;
+               }
+               bool neg = false;
+               if (x.Sign < 0) {
+                       x = -x;
+                       neg = (e & 1) != 0;
+               }
+               if (x.IsPowerOfTwo) {
+                       int t = x.BitLength - 1;
+                       long u = (long)t * (long)e;
+                       if (u > (long)Int32.MaxValue) {
+                               throw new OverflowException();
+                       }
+                       x = One << (int)u;
+               } else {
+                       ZInt y = One;
+                       for (;;) {
+                               if ((e & 1) != 0) {
+                                       y *= x;
+                               }
+                               e >>= 1;
+                               if (e == 0) {
+                                       break;
+                               }
+                               x *= x;
+                       }
+                       x = y;
+               }
+               return neg ? -x : x;
+       }
+
+       /*
+        * Modular exponentation: this function raises v to the power e
+        * modulo m. The returned value is reduced modulo m: it will be
+        * in the 0 to abs(m)-1 range.
+        *
+        * The modulus m must be positive. If m is 1, then the result is
+        * 0 (regardless of the values of v and e).
+        *
+        * The exponent e must be nonnegative (this function does not
+        * compute modular inverses). If e is zero, then the result is 1
+        * (except if m is 1).
+        */
+       public static ZInt ModPow(ZInt v, ZInt e, ZInt m)
+       {
+               int se = e.Sign;
+               if (se < 0) {
+                       throw new ArgumentOutOfRangeException();
+               }
+               int sm = m.Sign;
+               if (sm < 0) {
+                       m = -m;
+               } else if (sm == 0) {
+                       throw new DivideByZeroException();
+               }
+               if (m.varray == null && m.small == 1) {
+                       return Zero;
+               }
+               if (se == 0) {
+                       return One;
+               }
+               if (v.IsZero) {
+                       return Zero;
+               }
+
+               // TODO: use Montgomery's multiplication when the exponent
+               // is large.
+               ZInt x = v.Mod(m);
+               for (int n = e.BitLength - 2; n >= 0; n --) {
+                       x = (x * x).Mod(m);
+                       if (e.TestBit(n)) {
+                               x = (x * v).Mod(m);
+                       }
+               }
+               return x;
+       }
+
+       /*
+        * Get the absolute value of a ZInt.
+        */
+       public static ZInt Abs(ZInt x)
+       {
+               return (x.Sign < 0) ? -x : x;
+       }
+
+       private static void AppendHex(StringBuilder sb, uint w, bool trim)
+       {
+               int i = 28;
+               if (trim) {
+                       for (; i >= 0 && (w >> i) == 0; i -= 4);
+                       if (i < 0) {
+                               sb.Append("0");
+                               return;
+                       }
+               }
+               for (; i >= 0; i -= 4) {
+                       sb.Append("0123456789ABCDEF"[(int)((w >> i) & 0x0F)]);
+               }
+       }
+
+       /*
+        * Convert this value to hexadecimal. If this instance is zero,
+        * then "0" is returned. Otherwise, the number of digits is
+        * minimal (no leading '0'). A leading '-' is used for negative
+        * values. Hexadecimal digits are uppercase.
+        */
+       public string ToHexString()
+       {
+               if (varray == null && small == 0) {
+                       return "0";
+               }
+               StringBuilder sb = new StringBuilder();
+               ZInt x = this;
+               if (x.small < 0) {
+                       sb.Append('-');
+                       x = -x;
+               }
+               if (x.varray == null) {
+                       AppendHex(sb, (uint)x.small, true);
+               } else {
+                       int n = x.varray.Length;
+                       AppendHex(sb, x.varray[n - 1], true);
+                       for (int j = n - 2; j >= 0; j --) {
+                               AppendHex(sb, x.varray[j], false);
+                       }
+               }
+               return sb.ToString();
+       }
+
+       /*
+        * Convert this value to decimal. A leading '-' sign is used for
+        * negative value. The number of digits is minimal (no leading '0',
+        * except for zero, which is returned as "0").
+        */
+       public override string ToString()
+       {
+               return ToString(10);
+       }
+
+       private static int[] NDIGITS32 = {
+               0, 0, 31, 20, 15, 13, 12, 11, 10, 10, 9, 9, 8, 8, 8, 8, 7, 7,
+               7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6
+       };
+
+       private static uint[] RADIXPP32 = {
+               0, 0, 2147483648, 3486784401, 1073741824, 1220703125,
+               2176782336, 1977326743, 1073741824, 3486784401, 1000000000,
+               2357947691, 429981696, 815730721, 1475789056, 2562890625,
+               268435456, 410338673, 612220032, 893871739, 1280000000,
+               1801088541, 2494357888, 3404825447, 191102976, 244140625,
+               308915776, 387420489, 481890304, 594823321, 729000000,
+               887503681, 1073741824, 1291467969, 1544804416, 1838265625,
+               2176782336
+       };
+
+       private static char[] DCHAR =
+               "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
+
+       static void AppendDigits(StringBuilder sb, uint v, int radix, int num)
+       {
+               while (num -- > 0) {
+                       sb.Append(DCHAR[v % (uint)radix]);
+                       v = v / (uint)radix;
+               }
+       }
+
+       static void AppendDigits(StringBuilder sb, uint v, int radix)
+       {
+               while (v > 0) {
+                       sb.Append(DCHAR[v % (uint)radix]);
+                       v = v / (uint)radix;
+               }
+       }
+
+       /*
+        * Convert this value to a string, in the provided radix. The
+        * radix must be in the 2 to 36 range (inclusive); uppercase
+        * letters 'A' to 'Z' are used for digits of value 10 to 35.
+        * If the value is zero, then "0" is returned; otherwise, leading
+        * '0' digits are removed. A leading '-' sign is added for negative
+        * values.
+        */
+       public string ToString(int radix)
+       {
+               if (radix < 2 || radix > 36) {
+                       throw new ArgumentOutOfRangeException();
+               }
+
+               /*
+                * Special optimized case for base 16.
+                */
+               if (radix == 16) {
+                       return ToHexString();
+               }
+
+               if (IsZero) {
+                       return "0";
+               }
+
+               ZInt x = this;
+               if (x.Sign < 0) {
+                       x = -x;
+               }
+               StringBuilder sb = new StringBuilder();
+               if (x.varray == null) {
+                       AppendDigits(sb, (uint)x.small, radix);
+               } else {
+                       uint[] m = new uint[x.varray.Length];
+                       Array.Copy(x.varray, 0, m, 0, m.Length);
+                       uint prad = RADIXPP32[radix];
+                       int dnum = NDIGITS32[radix];
+                       for (;;) {
+                               uint v = MutateDivRem(m, prad);
+                               bool qz = Length(0, m) == 0;
+                               if (qz) {
+                                       AppendDigits(sb, v, radix);
+                                       break;
+                               } else {
+                                       AppendDigits(sb, v, radix, dnum);
+                               }
+                       }
+               }
+               if (Sign < 0) {
+                       sb.Append('-');
+               }
+               return Reverse(sb);
+       }
+
+       static string Reverse(StringBuilder sb)
+       {
+               int n = sb.Length;
+               char[] tc = new char[n];
+               sb.CopyTo(0, tc, 0, n);
+               for (int i = 0, j = n - 1; i < j; i ++, j --) {
+                       char c = tc[i];
+                       tc[i] = tc[j];
+                       tc[j] = c;
+               }
+               return new string(tc);
+       }
+
+       static uint DigitValue(char c, int radix)
+       {
+               int d;
+               if (c >= '0' && c <= '9') {
+                       d = c - '0';
+               } else if (c >= 'a' && c <= 'z') {
+                       d = (c - 'a') + 10;
+               } else if (c >= 'A' && c <= 'Z') {
+                       d = (c - 'A') + 10;
+               } else {
+                       d = -1;
+               }
+               if (d < 0 || d >= radix) {
+                       throw new ArgumentException();
+               }
+               return (uint)d;
+       }
+
+       static ZInt ParseUnsigned(string s, int radix)
+       {
+               if (s.Length == 0) {
+                       throw new ArgumentException();
+               }
+               ZInt x = Zero;
+               uint acc = 0;
+               int accNum = 0;
+               uint prad = RADIXPP32[radix];
+               int dnum = NDIGITS32[radix];
+               foreach (char c in s) {
+                       uint d = DigitValue(c, radix);
+                       acc = acc * (uint)radix + d;
+                       if (++ accNum == dnum) {
+                               x = x * (ZInt)prad + (ZInt)acc;
+                               acc = 0;
+                               accNum = 0;
+                       }
+               }
+               if (accNum > 0) {
+                       uint p = 1;
+                       while (accNum -- > 0) {
+                               p *= (uint)radix;
+                       }
+                       x = x * (ZInt)p + (ZInt)acc;
+               }
+               return x;
+       }
+
+       /*
+        * Parse a string:
+        * -- A leading '-' is allowed, to denote a negative value.
+        * -- If there is a "0b" or "0B" header (after any '-' sign),
+        *    then the value is interpreted in base 2.
+        * -- If there is a "0x" or "0X" header (after any '-' sign),
+        *    then the value is interpreted in base 16 (hexadecimal).
+        *    Both uppercase and lowercase letters are accepted.
+        * -- If there is no header, then decimal interpretation is used.
+        *
+        * Unexpected characters (including spaces) trigger exceptions.
+        * There must be at least one digit.
+        */
+       public static ZInt Parse(string s)
+       {
+               s = s.Trim();
+               bool neg = false;
+               if (s.StartsWith("-")) {
+                       neg = true;
+                       s = s.Substring(1);
+               }
+               int radix;
+               if (s.StartsWith("0b") || s.StartsWith("0B")) {
+                       radix = 2;
+                       s = s.Substring(2);
+               } else if (s.StartsWith("0x") || s.StartsWith("0X")) {
+                       radix = 16;
+                       s = s.Substring(2);
+               } else {
+                       radix = 10;
+               }
+               ZInt x = ParseUnsigned(s, radix);
+               return neg ? -x : x;
+       }
+
+       /*
+        * Parse a string in the specified radix. The radix must be in
+        * the 2 to 36 range (inclusive). Uppercase and lowercase letters
+        * are accepted for digits in the 10 to 35 range.
+        *
+        * A leading '-' sign is allowed, to denote a negative value.
+        * Otherwise, only digits (acceptable with regards to the radix)
+        * may appear. There must be at least one digit.
+        */
+       public static ZInt Parse(string s, int radix)
+       {
+               if (radix < 2 || radix > 36) {
+                       throw new ArgumentOutOfRangeException();
+               }
+               s = s.Trim();
+               bool neg = false;
+               if (s.StartsWith("-")) {
+                       neg = true;
+                       s = s.Substring(1);
+               }
+               ZInt x = ParseUnsigned(s, radix);
+               return neg ? -x : x;
+       }
+
+       static uint DecU32BE(byte[] buf, int off, int len)
+       {
+               switch (len) {
+               case 0:
+                       return 0;
+               case 1:
+                       return buf[off];
+               case 2:
+                       return ((uint)buf[off] << 8)
+                               | (uint)buf[off + 1];
+               case 3:
+                       return ((uint)buf[off] << 16)
+                               | ((uint)buf[off + 1] << 8)
+                               | (uint)buf[off + 2];
+               default:
+                       return ((uint)buf[off] << 24)
+                               | ((uint)buf[off + 1] << 16)
+                               | ((uint)buf[off + 2] << 8)
+                               | (uint)buf[off + 3];
+               }
+       }
+
+       /*
+        * Decode an integer, assuming unsigned big-endian encoding.
+        * An empty array is decoded as 0.
+        */
+       public static ZInt DecodeUnsignedBE(byte[] buf)
+       {
+               return DecodeUnsignedBE(buf, 0, buf.Length);
+       }
+
+       /*
+        * Decode an integer, assuming unsigned big-endian encoding.
+        * An empty array is decoded as 0.
+        */
+       public static ZInt DecodeUnsignedBE(byte[] buf, int off, int len)
+       {
+               while (len > 0 && buf[off] == 0) {
+                       off ++;
+                       len --;
+               }
+               if (len == 0) {
+                       return Zero;
+               } else if (len <= 4) {
+                       return new ZInt(DecU32BE(buf, off, len));
+               }
+               uint[] m = new uint[(len + 3) >> 2];
+               int i = 0;
+               for (int j = len; j > 0; j -= 4) {
+                       int k = j - 4;
+                       uint w;
+                       if (k < 0) {
+                               w = DecU32BE(buf, off, j);
+                       } else {
+                               w = DecU32BE(buf, off + k, 4);
+                       }
+                       m[i ++] = w;
+               }
+               return new ZInt(0, m);
+       }
+
+       static RNGCryptoServiceProvider RNG = new RNGCryptoServiceProvider();
+
+       /*
+        * Create a random integer of the provided size. Returned value
+        * is in the 0 (inclusive) to 2^size (exclusive) range. A
+        * cryptographically strong RNG is used to ensure uniform selection.
+        */
+       public static ZInt MakeRand(int size)
+       {
+               if (size <= 0) {
+                       throw new ArgumentOutOfRangeException();
+               }
+               byte[] buf = new byte[(size + 7) >> 3];
+               RNG.GetBytes(buf);
+               int kb = size & 7;
+               if (kb != 0) {
+                       buf[0] &= (byte)(0xFF >> (8 - kb));
+               }
+               return DecodeUnsignedBE(buf);
+       }
+
+       /*
+        * Create a random integer in the 0 (inclusive) to max (exclusive)
+        * range. 'max' must be positive. A cryptographically strong RNG
+        * is used to ensure uniform selection.
+        */
+       public static ZInt MakeRand(ZInt max)
+       {
+               if (max.Sign <= 0) {
+                       throw new ArgumentOutOfRangeException();
+               }
+               int bl = max.BitLength;
+               for (;;) {
+                       ZInt x = MakeRand(bl);
+                       if (x < max) {
+                               return x;
+                       }
+               }
+       }
+
+       /*
+        * Create a random integer in the min (inclusive) to max (exclusive)
+        * range. 'max' must be greater than min. A cryptographically
+        * strong RNG is used to ensure uniform selection.
+        */
+       public static ZInt MakeRand(ZInt min, ZInt max)
+       {
+               if (max <= min) {
+                       throw new ArgumentOutOfRangeException();
+               }
+               return min + MakeRand(max - min);
+       }
+
+       /*
+        * Check whether this integer is prime. A probabilistic algorithm
+        * is used, that theoretically ensures that a non-prime won't be
+        * declared prime with probability greater than 2^(-100). Note that
+        * this holds regardless of how the integer was generated (this
+        * method does not assume that uniform random selection was used).
+        *
+        * (Realistically, the probability of a computer hardware
+        * malfunction is way greater than 2^(-100), so this property
+        * returns the primality status with as much certainty as can be
+        * achieved with a computer.)
+        */
+       public bool IsPrime {
+               get {
+                       return IsProbablePrime(50);
+               }
+       }
+
+       static uint[] PRIMES_BF = new uint[] {
+               0xA08A28AC, 0x28208A20, 0x02088288, 0x800228A2,
+               0x20A00A08, 0x80282088, 0x800800A2, 0x08028228,
+               0x0A20A082, 0x22880020, 0x28020800, 0x88208082,
+               0x02022020, 0x08828028, 0x8008A202, 0x20880880
+       };
+
+       private bool IsProbablePrime(int rounds)
+       {
+               ZInt x = this;
+               int cc = x.Sign;
+               if (cc == 0) {
+                       return false;
+               } else if (cc < 0) {
+                       x = -x;
+               }
+               if (x.varray == null) {
+                       if (x.small < (PRIMES_BF.Length << 5)) {
+                               return (PRIMES_BF[x.small >> 5]
+                                       & ((uint)1 << (x.small & 31))) != 0;
+                       }
+               }
+               if (!x.TestBit(0)) {
+                       return false;
+               }
+
+               ZInt xm1 = x;
+               xm1 --;
+               ZInt m = xm1;
+               int a;
+               for (a = 0; !m.TestBit(a); a ++);
+               m >>= a;
+               while (rounds -- > 0) {
+                       ZInt b = MakeRand(Two, x);
+                       ZInt z = ModPow(b, m, x);
+                       for (int j = 0; j < a; j ++) {
+                               if (z == One) {
+                                       if (j > 0) {
+                                               return false;
+                                       }
+                                       break;
+                               }
+                               if (z == xm1) {
+                                       break;
+                               }
+                               if ((j + 1) < a) {
+                                       z = (z * z) % x;
+                               } else {
+                                       return false;
+                               }
+                       }
+               }
+               return true;
+       }
+
+       /*
+        * Encode this integer as bytes (signed big-endian convention).
+        * Encoding is of minimal length that still contains a sign bit
+        * (compatible with ASN.1 DER encoding).
+        */
+       public byte[] ToBytesBE()
+       {
+               byte[] r = new byte[(BitLength + 8) >> 3];
+               ToBytesBE(r, 0, r.Length);
+               return r;
+       }
+
+       /*
+        * Encode this integer as bytes (signed little-endian convention).
+        * Encoding is of minimal length that still contains a sign bit.
+        */
+       public byte[] ToBytesLE()
+       {
+               byte[] r = new byte[(BitLength + 8) >> 3];
+               ToBytesLE(r, 0, r.Length);
+               return r;
+       }
+
+       /*
+        * Encode this integer as bytes (signed big-endian convention).
+        * Output length is provided; exactly that many bytes will be
+        * written. The value is sign-extended or truncated if needed.
+        */
+       public void ToBytesBE(byte[] buf, int off, int len)
+       {
+               ToBytes(true, buf, off, len);
+       }
+
+       /*
+        * Encode this integer as bytes (signed little-endian convention).
+        * Output length is provided; exactly that many bytes will be
+        * written. The value is sign-extended or truncated if needed.
+        */
+       public void ToBytesLE(byte[] buf, int off, int len)
+       {
+               ToBytes(false, buf, off, len);
+       }
+
+       /*
+        * Encode this integer as bytes (unsigned big-endian convention).
+        * Encoding is of minimal length, possibly without a sign bit. If
+        * this value is zero, then an empty array is returned. If this
+        * value is negative, then an ArgumentOutOfRangeException is thrown.
+        */
+       public byte[] ToBytesUnsignedBE()
+       {
+               if (Sign < 0) {
+                       throw new ArgumentOutOfRangeException();
+               }
+               byte[] r = new byte[(BitLength + 7) >> 3];
+               ToBytesBE(r, 0, r.Length);
+               return r;
+       }
+
+       /*
+        * Encode this integer as bytes (unsigned little-endian convention).
+        * Encoding is of minimal length, possibly without a sign bit. If
+        * this value is zero, then an empty array is returned. If this
+        * value is negative, then an ArgumentOutOfRangeException is thrown.
+        */
+       public byte[] ToBytesUnsignedLE()
+       {
+               if (Sign < 0) {
+                       throw new ArgumentOutOfRangeException();
+               }
+               byte[] r = new byte[(BitLength + 7) >> 3];
+               ToBytesLE(r, 0, r.Length);
+               return r;
+       }
+
+       void ToBytes(bool be, byte[] buf, int off, int len)
+       {
+               uint iw = (uint)small >> 31;
+               for (int i = 0; i < len; i ++) {
+                       int j = i >> 2;
+                       uint w;
+                       if (varray == null) {
+                               w = (j == 0) ? (uint)small : iw;
+                       } else {
+                               w = (j < varray.Length) ? varray[j] : iw;
+                       }
+                       byte v = (byte)(w >> ((i & 3) << 3));
+                       if (be) {
+                               buf[off + len - 1 - i] = v;
+                       } else {
+                               buf[off + i] = v;
+                       }
+               }
+       }
+}
diff --git a/build.cmd b/build.cmd
new file mode 100644 (file)
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 (executable)
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 (file)
index 0000000..d46f349
--- /dev/null
@@ -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 (file)
index 0000000..6de3d2f
--- /dev/null
@@ -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 (file)
index 0000000..524b29c
--- /dev/null
@@ -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 (file)
index 0000000..b18b3f4
--- /dev/null
@@ -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 (file)
index 0000000..8d6301c
--- /dev/null
@@ -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-----