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");
+               }
+
+  &nb