TlsProtocolHandler.cs :  » PDF » iTextSharp » Org » BouncyCastle » Crypto » Tls » C# / CSharp Open Source

Home
C# / CSharp Open Source
1.2.6.4 mono .net core
2.2.6.4 mono core
3.Aspect Oriented Frameworks
4.Bloggers
5.Build Systems
6.Business Application
7.Charting Reporting Tools
8.Chat Servers
9.Code Coverage Tools
10.Content Management Systems CMS
11.CRM ERP
12.Database
13.Development
14.Email
15.Forum
16.Game
17.GIS
18.GUI
19.IDEs
20.Installers Generators
21.Inversion of Control Dependency Injection
22.Issue Tracking
23.Logging Tools
24.Message
25.Mobile
26.Network Clients
27.Network Servers
28.Office
29.PDF
30.Persistence Frameworks
31.Portals
32.Profilers
33.Project Management
34.RSS RDF
35.Rule Engines
36.Script
37.Search Engines
38.Sound Audio
39.Source Control
40.SQL Clients
41.Template Engines
42.Testing
43.UML
44.Web Frameworks
45.Web Service
46.Web Testing
47.Wiki Engines
48.Windows Presentation Foundation
49.Workflows
50.XML Parsers
C# / C Sharp
C# / C Sharp by API
C# / CSharp Tutorial
C# / CSharp Open Source » PDF » iTextSharp 
iTextSharp » Org » BouncyCastle » Crypto » Tls » TlsProtocolHandler.cs
using System;
using System.Collections;
using System.IO;
using System.Text;

using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto.Agreement;
using Org.BouncyCastle.Crypto.Agreement.Srp;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Encodings;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.IO;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.Utilities.Date;

namespace Org.BouncyCastle.Crypto.Tls{
  /// <remarks>An implementation of all high level protocols in TLS 1.0.</remarks>
  public class TlsProtocolHandler
  {
    private const short RL_CHANGE_CIPHER_SPEC = 20;
    private const short RL_ALERT = 21;
    private const short RL_HANDSHAKE = 22;
    private const short RL_APPLICATION_DATA = 23;

    /*
    hello_request(0), client_hello(1), server_hello(2),
    certificate(11), server_key_exchange (12),
    certificate_request(13), server_hello_done(14),
    certificate_verify(15), client_key_exchange(16),
    finished(20), (255)
    */

    private const short HP_HELLO_REQUEST = 0;
    private const short HP_CLIENT_HELLO = 1;
    private const short HP_SERVER_HELLO = 2;
    private const short HP_CERTIFICATE = 11;
    private const short HP_SERVER_KEY_EXCHANGE = 12;
    private const short HP_CERTIFICATE_REQUEST = 13;
    private const short HP_SERVER_HELLO_DONE = 14;
    private const short HP_CERTIFICATE_VERIFY = 15;
    private const short HP_CLIENT_KEY_EXCHANGE = 16;
    private const short HP_FINISHED = 20;

    /*
    * Our Connection states
    */

    private const short CS_CLIENT_HELLO_SEND = 1;
    private const short CS_SERVER_HELLO_RECEIVED = 2;
    private const short CS_SERVER_CERTIFICATE_RECEIVED = 3;
    private const short CS_SERVER_KEY_EXCHANGE_RECEIVED = 4;
    private const short CS_CERTIFICATE_REQUEST_RECEIVED = 5;
    private const short CS_SERVER_HELLO_DONE_RECEIVED = 6;
    private const short CS_CLIENT_KEY_EXCHANGE_SEND = 7;
    private const short CS_CERTIFICATE_VERIFY_SEND = 8;
    private const short CS_CLIENT_CHANGE_CIPHER_SPEC_SEND = 9;
    private const short CS_CLIENT_FINISHED_SEND = 10;
    private const short CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED = 11;
    private const short CS_DONE = 12;

    internal const short AP_close_notify = 0;
    internal const short AP_unexpected_message = 10;
    internal const short AP_bad_record_mac = 20;
    internal const short AP_decryption_failed = 21;
    internal const short AP_record_overflow = 22;
    internal const short AP_decompression_failure = 30;
    internal const short AP_handshake_failure = 40;
    internal const short AP_bad_certificate = 42;
    internal const short AP_unsupported_certificate = 43;
    internal const short AP_certificate_revoked = 44;
    internal const short AP_certificate_expired = 45;
    internal const short AP_certificate_unknown = 46;
    internal const short AP_illegal_parameter = 47;
    internal const short AP_unknown_ca = 48;
    internal const short AP_access_denied = 49;
    internal const short AP_decode_error = 50;
    internal const short AP_decrypt_error = 51;
    internal const short AP_export_restriction = 60;
    internal const short AP_protocol_version = 70;
    internal const short AP_insufficient_security = 71;
    internal const short AP_internal_error = 80;
    internal const short AP_user_canceled = 90;
    internal const short AP_no_renegotiation = 100;

    internal const short AL_warning = 1;
    internal const short AL_fatal = 2;

    private static readonly byte[] emptybuf = new byte[0];

    private static readonly string TLS_ERROR_MESSAGE = "Internal TLS error, this could be an attack";

    /*
    * Queues for data from some protocols.
    */

    private ByteQueue applicationDataQueue = new ByteQueue();
    private ByteQueue changeCipherSpecQueue = new ByteQueue();
    private ByteQueue alertQueue = new ByteQueue();
    private ByteQueue handshakeQueue = new ByteQueue();

    /*
    * The Record Stream we use
    */
    private RecordStream rs;

    private SecureRandom random;

    /*
     * The public key of the server.
     */
    private AsymmetricKeyParameter serverPublicKey = null;

    /*
     * The private key of the client (if provided)
     */
    private AsymmetricKeyParameter clientPrivateKey = null;

    private TlsInputStream tlsInputStream = null;
    private TlsOuputStream tlsOutputStream = null;

    private bool closed = false;
    private bool failedWithError = false;
    private bool appDataReady = false;
    private bool extendedClientHello;

    private byte[] clientRandom;
    private byte[] serverRandom;
    private byte[] ms;

    private TlsCipherSuite chosenCipherSuite = null;

    private BigInteger SRP_A;
    private byte[] SRP_identity = null, SRP_password = null;
    private BigInteger Yc;
    private byte[] pms;

    private ICertificateVerifyer verifyer = null;
    private Certificate clientCert = null;
    private TlsSigner clientSigner = null;

    /*
    * Both streams can be the same object
    */
    public TlsProtocolHandler(
      Stream  inStr,
      Stream  outStr)
    {
      /*
       * We use a threaded seed generator to generate a good random
       * seed. If the user has a better random seed, he should use
       * the constructor with a SecureRandom.
       * 
       * Hopefully, 20 bytes in fast mode are good enough.
       */
      byte[] seed = new ThreadedSeedGenerator().GenerateSeed(20, true);

      this.random = new SecureRandom(seed);
      this.rs = new RecordStream(this, inStr, outStr);
    }

    public TlsProtocolHandler(
      Stream      inStr,
      Stream      outStr,
      SecureRandom  sr)
    {
      this.random = sr;
      this.rs = new RecordStream(this, inStr, outStr);
    }

    internal SecureRandom Random
    {
      get { return random; }
    }

    private short connection_state;

    internal void ProcessData(
      short  protocol,
      byte[]  buf,
      int    offset,
      int    len)
    {
      /*
      * Have a look at the protocol type, and add it to the correct queue.
      */
      switch (protocol)
      {
        case RL_CHANGE_CIPHER_SPEC:
          changeCipherSpecQueue.AddData(buf, offset, len);
          processChangeCipherSpec();
          break;
        case RL_ALERT:
          alertQueue.AddData(buf, offset, len);
          processAlert();
          break;
        case RL_HANDSHAKE:
          handshakeQueue.AddData(buf, offset, len);
          processHandshake();
          break;
        case RL_APPLICATION_DATA:
          if (!appDataReady)
          {
            this.FailWithError(AL_fatal, AP_unexpected_message);
          }
          applicationDataQueue.AddData(buf, offset, len);
          processApplicationData();
          break;
        default:
          /*
          * Uh, we don't know this protocol.
          *
          * RFC2246 defines on page 13, that we should ignore this.
          */
          break;
      }
    }

    private void processHandshake()
    {
      bool read;
      do
      {
        read = false;

        /*
        * We need the first 4 bytes, they contain type and length of
        * the message.
        */
        if (handshakeQueue.Available >= 4)
        {
          byte[] beginning = new byte[4];
          handshakeQueue.Read(beginning, 0, 4, 0);
          MemoryStream bis = new MemoryStream(beginning, false);
          short type = TlsUtilities.ReadUint8(bis);
          int len = TlsUtilities.ReadUint24(bis);

          /*
          * Check if we have enough bytes in the buffer to read
          * the full message.
          */
          if (handshakeQueue.Available >= (len + 4))
          {
            /*
            * Read the message.
            */
            byte[] buf = new byte[len];
            handshakeQueue.Read(buf, 0, len, 4);
            handshakeQueue.RemoveData(len + 4);

            /*
            * If it is not a finished message, update our hashes
            * we prepare for the finish message.
            */
            if (type != HP_FINISHED)
            {
              rs.UpdateHandshakeData(beginning, 0, 4);
              rs.UpdateHandshakeData(buf, 0, len);
            }

            /*
            * Now, parse the message.
            */
            MemoryStream inStr = new MemoryStream(buf, false);

            /*
            * Check the type.
            */
            switch (type)
            {
              case HP_CERTIFICATE:
              {
                switch (connection_state)
                {
                  case CS_SERVER_HELLO_RECEIVED:
                  {
                    /*
                    * Parse the certificates.
                    */
                    Certificate cert = Certificate.Parse(inStr);
                    AssertEmpty(inStr);

                    X509CertificateStructure x509Cert = cert.certs[0];
                    SubjectPublicKeyInfo keyInfo = x509Cert.SubjectPublicKeyInfo;

                    try
                    {
                      this.serverPublicKey = PublicKeyFactory.CreateKey(keyInfo);
                    }
                    catch (Exception)
                    {
                      this.FailWithError(AL_fatal, AP_unsupported_certificate);
                    }

                    // Sanity check the PublicKeyFactory
                    if (this.serverPublicKey.IsPrivate)
                    {
                      this.FailWithError(AL_fatal, AP_internal_error);
                    }

                    /*
                    * Perform various checks per RFC2246 7.4.2
                    * TODO "Unless otherwise specified, the signing algorithm for the certificate
                    * must be the same as the algorithm for the certificate key."
                    */
                    switch (this.chosenCipherSuite.KeyExchangeAlgorithm)
                    {
                      case TlsCipherSuite.KE_RSA:
                        if (!(this.serverPublicKey is RsaKeyParameters))
                        {
                          this.FailWithError(AL_fatal, AP_certificate_unknown);
                        }
                        validateKeyUsage(x509Cert, KeyUsage.KeyEncipherment);
                        break;
                      case TlsCipherSuite.KE_DHE_RSA:
                      case TlsCipherSuite.KE_SRP_RSA:
                        if (!(this.serverPublicKey is RsaKeyParameters))
                        {
                          this.FailWithError(AL_fatal, AP_certificate_unknown);
                        }
                        validateKeyUsage(x509Cert, KeyUsage.DigitalSignature);
                        break;
                      case TlsCipherSuite.KE_DHE_DSS:
                      case TlsCipherSuite.KE_SRP_DSS:
                        if (!(this.serverPublicKey is DsaPublicKeyParameters))
                        {
                          this.FailWithError(AL_fatal, AP_certificate_unknown);
                        }
                        break;
                      default:
                        this.FailWithError(AL_fatal, AP_unsupported_certificate);
                        break;
                    }

                    /*
                    * Verify them.
                    */
                    if (!this.verifyer.IsValid(cert.GetCerts()))
                    {
                      this.FailWithError(AL_fatal, AP_user_canceled);
                    }

                    break;
                  }
                  default:
                    this.FailWithError(AL_fatal, AP_unexpected_message);
                    break;
                }

                connection_state = CS_SERVER_CERTIFICATE_RECEIVED;
                read = true;
                break;
              }
              case HP_FINISHED:
                switch (connection_state)
                {
                  case CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED:
                    /*
                    * Read the checksum from the finished message,
                    * it has always 12 bytes.
                    */
                    byte[] receivedChecksum = new byte[12];
                    TlsUtilities.ReadFully(receivedChecksum, inStr);
                    AssertEmpty(inStr);

                    /*
                    * Calculate our own checksum.
                    */
                    byte[] checksum = new byte[12];
                    byte[] md5andsha1 = new byte[16 + 20];
                    rs.hash2.DoFinal(md5andsha1, 0);
                    TlsUtilities.PRF(this.ms, "server finished", md5andsha1, checksum);

                    /*
                    * Compare both checksums.
                    */
                    for (int i = 0; i < receivedChecksum.Length; i++)
                    {
                      if (receivedChecksum[i] != checksum[i])
                      {
                        /*
                        * Wrong checksum in the finished message.
                        */
                        this.FailWithError(AL_fatal, AP_handshake_failure);
                      }
                    }

                    connection_state = CS_DONE;

                    /*
                    * We are now ready to receive application data.
                    */
                    this.appDataReady = true;
                    read = true;
                    break;
                  default:
                    this.FailWithError(AL_fatal, AP_unexpected_message);
                    break;
                }
                break;
              case HP_SERVER_HELLO:
                switch (connection_state)
                {
                  case CS_CLIENT_HELLO_SEND:
                    /*
                    * Read the server hello message
                    */
                    TlsUtilities.CheckVersion(inStr, this);

                    /*
                    * Read the server random
                    */
                    this.serverRandom = new byte[32];
                    TlsUtilities.ReadFully(this.serverRandom, inStr);

                    /*
                    * Currently, we don't support session ids
                    */
                    byte[] sessionId = TlsUtilities.ReadOpaque8(inStr);

                    /*
                    * Find out which ciphersuite the server has
                    * chosen. If we don't support this ciphersuite,
                    * the TlsCipherSuiteManager will throw an
                    * exception.
                    */
                    this.chosenCipherSuite = TlsCipherSuiteManager.GetCipherSuite(
                      TlsUtilities.ReadUint16(inStr), this);

                    /*
                    * We support only the null compression which
                    * means no compression.
                    */
                    short compressionMethod = TlsUtilities.ReadUint8(inStr);
                    if (compressionMethod != 0)
                    {
                      this.FailWithError(TlsProtocolHandler.AL_fatal, TlsProtocolHandler.AP_illegal_parameter);
                    }

                                      /*
                                       * RFC4366 2.2
                                       * The extended server hello message format MAY be sent
                                       * in place of the server hello message when the client
                                       * has requested extended functionality via the extended
                                       * client hello message specified in Section 2.1.
                                       */
                                      if (extendedClientHello && inStr.Position < inStr.Length)
                                      {
                                          // Process extensions from extended server hello
                                          byte[] extBytes = TlsUtilities.ReadOpaque16(inStr);
  
                                          // Int32 -> byte[]
                                          Hashtable serverExtensions = new Hashtable();

                                          MemoryStream ext = new MemoryStream(extBytes, false);
                                          while (ext.Position < ext.Length)
                                          {
                                              int extType = TlsUtilities.ReadUint16(ext);
                                              byte[] extValue = TlsUtilities.ReadOpaque16(ext);

                                              serverExtensions[extType] = extValue;
                                          }

                                          // TODO Validate/process serverExtensions (via client?)
                                          // TODO[SRP]
                                      }

                    /*
                    * Process any extensions
                    */
                    // TODO[SRP]
//                    if (inStr.Position < inStr.Length)
//                    {
//                      int extensionsLength = TlsUtilities.ReadUint16(inStr);
//                      byte[] extensions = new byte[extensionsLength];
//                      TlsUtilities.ReadFully(extensions, inStr);
//
//                      // TODO Validate/process
//                    }

                    AssertEmpty(inStr);

                    connection_state = CS_SERVER_HELLO_RECEIVED;
                    read = true;
                    break;
                  default:
                    this.FailWithError(AL_fatal, AP_unexpected_message);
                    break;
                }
                break;
              case HP_SERVER_HELLO_DONE:
                switch (connection_state)
                {
                  case CS_SERVER_CERTIFICATE_RECEIVED:
                  case CS_SERVER_KEY_EXCHANGE_RECEIVED:
                  case CS_CERTIFICATE_REQUEST_RECEIVED:

                    // NB: Original code used case label fall-through
                    if (connection_state == CS_SERVER_CERTIFICATE_RECEIVED)
                    {
                      /*
                      * There was no server key exchange message, check
                      * that we are doing RSA key exchange.
                      */
                      if (this.chosenCipherSuite.KeyExchangeAlgorithm != TlsCipherSuite.KE_RSA)
                      {
                        this.FailWithError(AL_fatal, AP_unexpected_message);
                      }
                    }

                    AssertEmpty(inStr);
                    bool isCertReq = (connection_state == CS_CERTIFICATE_REQUEST_RECEIVED);
                    connection_state = CS_SERVER_HELLO_DONE_RECEIVED;

                    if (isCertReq)
                    {
                      sendClientCertificate();
                    }

                    /*
                    * Send the client key exchange message, depending
                    * on the key exchange we are using in our
                    * ciphersuite.
                    */
                    switch (this.chosenCipherSuite.KeyExchangeAlgorithm)
                    {
                      case TlsCipherSuite.KE_RSA:
                      {
                        /*
                        * We are doing RSA key exchange. We will
                        * choose a pre master secret and send it
                        * rsa encrypted to the server.
                        *
                        * Prepare pre master secret.
                        */
                        pms = new byte[48];
                        pms[0] = 3;
                        pms[1] = 1;
                        random.NextBytes(pms, 2, 46);

                        /*
                        * Encode the pms and send it to the server.
                        *
                        * Prepare an Pkcs1Encoding with good random
                        * padding.
                        */
                        RsaBlindedEngine rsa = new RsaBlindedEngine();
                        Pkcs1Encoding encoding = new Pkcs1Encoding(rsa);
                        encoding.Init(true, new ParametersWithRandom(this.serverPublicKey, this.random));
                        byte[] encrypted = null;
                        try
                        {
                          encrypted = encoding.ProcessBlock(pms, 0, pms.Length);
                        }
                        catch (InvalidCipherTextException)
                        {
                          /*
                          * This should never happen, only during decryption.
                          */
                          this.FailWithError(AL_fatal, AP_internal_error);
                        }

                        /*
                        * Send the encrypted pms.
                        */
                        sendClientKeyExchange(encrypted);
                        break;
                      }
                      case TlsCipherSuite.KE_DHE_DSS:
                      case TlsCipherSuite.KE_DHE_RSA:
                      {
                        /*
                        * Send the Client Key Exchange message for
                        * DHE key exchange.
                        */
                        byte[] YcByte = BigIntegers.AsUnsignedByteArray(this.Yc);

                        sendClientKeyExchange(YcByte);

                        break;
                      }
                      case TlsCipherSuite.KE_SRP:
                      case TlsCipherSuite.KE_SRP_RSA:
                      case TlsCipherSuite.KE_SRP_DSS:
                      {
                        /*
                        * Send the Client Key Exchange message for
                        * SRP key exchange.
                        */
                        byte[] bytes = BigIntegers.AsUnsignedByteArray(this.SRP_A);

                        sendClientKeyExchange(bytes);

                        break;
                      }
                      default:
                        /*
                        * Problem during handshake, we don't know
                        * how to handle this key exchange method.
                        */
                        this.FailWithError(AL_fatal, AP_unexpected_message);
                        break;

                    }

                    connection_state = CS_CLIENT_KEY_EXCHANGE_SEND;

                    if (isCertReq && this.clientPrivateKey != null)
                    {
                        sendCertificateVerify();

                        connection_state = CS_CERTIFICATE_VERIFY_SEND;
                    }

                    /*
                    * Now, we send change cipher state
                    */
                    byte[] cmessage = new byte[1];
                    cmessage[0] = 1;
                    rs.WriteMessage(RL_CHANGE_CIPHER_SPEC, cmessage, 0, cmessage.Length);

                    connection_state = CS_CLIENT_CHANGE_CIPHER_SPEC_SEND;

                    /*
                    * Calculate the ms
                    */
                    this.ms = new byte[48];
                    byte[] randBytes = new byte[clientRandom.Length + serverRandom.Length];
                    Array.Copy(clientRandom, 0, randBytes, 0, clientRandom.Length);
                    Array.Copy(serverRandom, 0, randBytes, clientRandom.Length, serverRandom.Length);
                    TlsUtilities.PRF(pms, "master secret", randBytes, this.ms);

                    /*
                    * Initialize our cipher suite
                    */
                    rs.writeSuite = this.chosenCipherSuite;
                    rs.writeSuite.Init(this, this.ms, clientRandom, serverRandom);

                    /*
                    * Send our finished message.
                    */
                    byte[] checksum = new byte[12];
                    byte[] md5andsha1 = new byte[16 + 20];
                    rs.hash1.DoFinal(md5andsha1, 0);
                    TlsUtilities.PRF(this.ms, "client finished", md5andsha1, checksum);

                    MemoryStream bos2 = new MemoryStream();
                    TlsUtilities.WriteUint8(HP_FINISHED, bos2);
                    TlsUtilities.WriteUint24(12, bos2);
                    bos2.Write(checksum, 0, checksum.Length);
                    byte[] message2 = bos2.ToArray();

                    rs.WriteMessage(RL_HANDSHAKE, message2, 0, message2.Length);

                    this.connection_state = CS_CLIENT_FINISHED_SEND;
                    read = true;
                    break;
                  default:
                    this.FailWithError(AL_fatal, AP_handshake_failure);
                    break;
                }
                break;
              case HP_SERVER_KEY_EXCHANGE:
              {
                switch (connection_state)
                {
                  case CS_SERVER_HELLO_RECEIVED:
                  case CS_SERVER_CERTIFICATE_RECEIVED:
                  {
                    // NB: Original code used case label fall-through
                    if (connection_state == CS_SERVER_HELLO_RECEIVED)
                    {
                      /*
                      * There was no server certificate message, check
                      * that we are doing SRP key exchange.
                      */
                      if (this.chosenCipherSuite.KeyExchangeAlgorithm != TlsCipherSuite.KE_SRP)
                      {
                        this.FailWithError(AL_fatal, AP_unexpected_message);
                      }
                    }

                    /*
                    * Check that we are doing DHE key exchange
                    */
                    switch (this.chosenCipherSuite.KeyExchangeAlgorithm)
                    {
                      case TlsCipherSuite.KE_DHE_RSA:
                      {
                        processDHEKeyExchange(inStr, new TlsRsaSigner());
                        break;
                      }
                      case TlsCipherSuite.KE_DHE_DSS:
                      {
                        processDHEKeyExchange(inStr, new TlsDssSigner());
                        break;
                      }
                      case TlsCipherSuite.KE_SRP:
                      {
                        processSRPKeyExchange(inStr, null);
                        break;
                      }
                      case TlsCipherSuite.KE_SRP_RSA:
                      {
                        processSRPKeyExchange(inStr, new TlsRsaSigner());
                        break;
                      }
                      case TlsCipherSuite.KE_SRP_DSS:
                      {
                        processSRPKeyExchange(inStr, new TlsDssSigner());
                        break;
                      }
                      default:
                        this.FailWithError(AL_fatal, AP_unexpected_message);
                        break;
                    }
                    break;
                  }
                  default:
                    this.FailWithError(AL_fatal, AP_unexpected_message);
                    break;
                }

                this.connection_state = CS_SERVER_KEY_EXCHANGE_RECEIVED;
                read = true;
                break;
              }
              case HP_CERTIFICATE_REQUEST:
                switch (connection_state)
                {
                  case CS_SERVER_CERTIFICATE_RECEIVED:
                  case CS_SERVER_KEY_EXCHANGE_RECEIVED:
                  {
                    // NB: Original code used case label fall-through
                    if (connection_state == CS_SERVER_CERTIFICATE_RECEIVED)
                    {
                      /*
                      * There was no server key exchange message, check
                      * that we are doing RSA key exchange.
                      */
                      if (this.chosenCipherSuite.KeyExchangeAlgorithm != TlsCipherSuite.KE_RSA)
                      {
                        this.FailWithError(AL_fatal, AP_unexpected_message);
                      }
                    }

                    byte[] types = TlsUtilities.ReadOpaque8(inStr);
                    byte[] auths = TlsUtilities.ReadOpaque16(inStr);

                    // TODO Validate/process

                    AssertEmpty(inStr);
                    break;
                  }
                  default:
                    this.FailWithError(AL_fatal, AP_unexpected_message);
                    break;
                }

                this.connection_state = CS_CERTIFICATE_REQUEST_RECEIVED;
                read = true;
                break;
              case HP_HELLO_REQUEST:
              case HP_CLIENT_KEY_EXCHANGE:
              case HP_CERTIFICATE_VERIFY:
              case HP_CLIENT_HELLO:
              default:
                // We do not support this!
                this.FailWithError(AL_fatal, AP_unexpected_message);
                break;
            }
          }
        }
      }
      while (read);
    }

    private void processApplicationData()
    {
      /*
      * There is nothing we need to do here.
      * 
      * This function could be used for callbacks when application
      * data arrives in the future.
      */
    }

    private void processAlert()
    {
      while (alertQueue.Available >= 2)
      {
        /*
        * An alert is always 2 bytes. Read the alert.
        */
        byte[] tmp = new byte[2];
        alertQueue.Read(tmp, 0, 2, 0);
        alertQueue.RemoveData(2);
        short level = tmp[0];
        short description = tmp[1];
        if (level == AL_fatal)
        {
          /*
          * This is a fatal error.
          */
          this.failedWithError = true;
          this.closed = true;
          /*
          * Now try to Close the stream, ignore errors.
          */
          try
          {
            rs.Close();
          }
          catch (Exception)
          {
          }
          throw new IOException(TLS_ERROR_MESSAGE);
        }
        else
        {
          /*
          * This is just a warning.
          */
          if (description == AP_close_notify)
          {
            /*
            * Close notify
            */
            this.FailWithError(AL_warning, AP_close_notify);
          }
          /*
          * If it is just a warning, we continue.
          */
        }
      }
    }

    /**
    * This method is called, when a change cipher spec message is received.
    *
    * @throws IOException If the message has an invalid content or the
    *                     handshake is not in the correct state.
    */
    private void processChangeCipherSpec()
    {
      while (changeCipherSpecQueue.Available > 0)
      {
        /*
        * A change cipher spec message is only one byte with the value 1.
        */
        byte[] b = new byte[1];
        changeCipherSpecQueue.Read(b, 0, 1, 0);
        changeCipherSpecQueue.RemoveData(1);
        if (b[0] != 1)
        {
          /*
          * This should never happen.
          */
          this.FailWithError(AL_fatal, AP_unexpected_message);

        }
        else
        {
          /*
          * Check if we are in the correct connection state.
          */
          if (this.connection_state == CS_CLIENT_FINISHED_SEND)
          {
            rs.readSuite = rs.writeSuite;
            this.connection_state = CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED;
          }
          else
          {
            /*
            * We are not in the correct connection state.
            */
            this.FailWithError(AL_fatal, AP_handshake_failure);
          }

        }
      }
    }

    private void processDHEKeyExchange(
      MemoryStream  inStr,
      TlsSigner    tlsSigner)
    {
      Stream sigIn = inStr;
      ISigner signer = null;
      if (tlsSigner != null)
      {
        signer = tlsSigner.CreateSigner();
        signer.Init(false, this.serverPublicKey);
        signer.BlockUpdate(this.clientRandom, 0, this.clientRandom.Length);
        signer.BlockUpdate(this.serverRandom, 0, this.serverRandom.Length);

        sigIn = new SignerStream(inStr, signer, null);
      }

      /*
      * Parse the Structure
      */
      byte[] pByte = TlsUtilities.ReadOpaque16(sigIn);
      byte[] gByte = TlsUtilities.ReadOpaque16(sigIn);
      byte[] YsByte = TlsUtilities.ReadOpaque16(sigIn);

      if (signer != null)
      {
        byte[] sigByte = TlsUtilities.ReadOpaque16(inStr);

        /*
        * Verify the Signature.
        */
        if (!signer.VerifySignature(sigByte))
        {
          this.FailWithError(AL_fatal, AP_bad_certificate);
        }
      }

      this.AssertEmpty(inStr);

      /*
      * Do the DH calculation.
      */
      BigInteger p = new BigInteger(1, pByte);
      BigInteger g = new BigInteger(1, gByte);
      BigInteger Ys = new BigInteger(1, YsByte);

      /*
      * Check the DH parameter values
      */
      if (!p.IsProbablePrime(10))
      {
        this.FailWithError(AL_fatal, AP_illegal_parameter);
      }
      if (g.CompareTo(BigInteger.Two) < 0 || g.CompareTo(p.Subtract(BigInteger.Two)) > 0)
      {
        this.FailWithError(AL_fatal, AP_illegal_parameter);
      }
      // TODO For static DH public values, see additional checks in RFC 2631 2.1.5 
      if (Ys.CompareTo(BigInteger.Two) < 0 || Ys.CompareTo(p.Subtract(BigInteger.One)) > 0)
      {
        this.FailWithError(AL_fatal, AP_illegal_parameter);
      }

      /*
      * Diffie-Hellman basic key agreement
      */
      DHParameters dhParams = new DHParameters(p, g);

      // Generate a keypair
      DHBasicKeyPairGenerator dhGen = new DHBasicKeyPairGenerator();
      dhGen.Init(new DHKeyGenerationParameters(random, dhParams));

      AsymmetricCipherKeyPair dhPair = dhGen.GenerateKeyPair();

      // Store the public value to send to server
      this.Yc = ((DHPublicKeyParameters)dhPair.Public).Y;

      // Calculate the shared secret
      DHBasicAgreement dhAgree = new DHBasicAgreement();
      dhAgree.Init(dhPair.Private);

      BigInteger agreement = dhAgree.CalculateAgreement(new DHPublicKeyParameters(Ys, dhParams));

      this.pms = BigIntegers.AsUnsignedByteArray(agreement);
    }

    private void processSRPKeyExchange(
      MemoryStream  inStr,
      TlsSigner    tlsSigner)
    {
      Stream sigIn = inStr;
      ISigner signer = null;
      if (tlsSigner != null)
      {
        signer = tlsSigner.CreateSigner();
        signer.Init(false, this.serverPublicKey);
        signer.BlockUpdate(this.clientRandom, 0, this.clientRandom.Length);
        signer.BlockUpdate(this.serverRandom, 0, this.serverRandom.Length);

        sigIn = new SignerStream(inStr, signer, null);
      }

      /*
      * Parse the Structure
      */
      byte[] NByte = TlsUtilities.ReadOpaque16(sigIn);
      byte[] gByte = TlsUtilities.ReadOpaque16(sigIn);
      byte[] sByte = TlsUtilities.ReadOpaque8(sigIn);
      byte[] BByte = TlsUtilities.ReadOpaque16(sigIn);

      if (signer != null)
      {
        byte[] sigByte = TlsUtilities.ReadOpaque16(inStr);

        /*
        * Verify the Signature.
        */
        if (!signer.VerifySignature(sigByte))
        {
          this.FailWithError(AL_fatal, AP_bad_certificate);
        }
      }

      this.AssertEmpty(inStr);

      BigInteger N = new BigInteger(1, NByte);
      BigInteger g = new BigInteger(1, gByte);
      byte[] s = sByte;
      BigInteger B = new BigInteger(1, BByte);

      Srp6Client srpClient = new Srp6Client();
      srpClient.Init(N, g, new Sha1Digest(), random);

      this.SRP_A = srpClient.GenerateClientCredentials(s, this.SRP_identity,
        this.SRP_password);

      try
      {
        BigInteger S = srpClient.CalculateSecret(B);

        // TODO Check if this needs to be a fixed size
        this.pms = BigIntegers.AsUnsignedByteArray(S);
      }
      catch (CryptoException)
      {
        this.FailWithError(AL_fatal, AP_illegal_parameter);
      }
    }

    private void validateKeyUsage(
      X509CertificateStructure  c,
      int              keyUsageBits)
    {
      X509Extensions exts = c.TbsCertificate.Extensions;
      if (exts != null)
      {
        X509Extension ext = exts.GetExtension(X509Extensions.KeyUsage);
        if (ext != null)
        {
          DerBitString ku = KeyUsage.GetInstance(ext);
          int bits = ku.GetBytes()[0];
          if ((bits & keyUsageBits) != keyUsageBits)
          {
            this.FailWithError(AL_fatal, AP_certificate_unknown);
          }
        }
      }
    }

    private void sendClientCertificate()
    {
      MemoryStream bos = new MemoryStream();
      TlsUtilities.WriteUint8(HP_CERTIFICATE, bos);
      clientCert.Encode(bos);
      byte[] message = bos.ToArray();

      rs.WriteMessage(RL_HANDSHAKE, message, 0, message.Length);
    }

    private void sendClientKeyExchange(
      byte[] keData)
    {
      MemoryStream bos = new MemoryStream();
      TlsUtilities.WriteUint8(HP_CLIENT_KEY_EXCHANGE, bos);
      TlsUtilities.WriteUint24(keData.Length + 2, bos);
      TlsUtilities.WriteOpaque16(keData, bos);
      byte[] message = bos.ToArray();

      rs.WriteMessage(RL_HANDSHAKE, message, 0, message.Length);
    }

    private void sendCertificateVerify()
    {
      /*
       * Send signature of handshake messages so far to prove we are the owner of
       * the cert See RFC 2246 sections 4.7, 7.4.3 and 7.4.8
       */

      try
      {
        byte[] md5andsha1 = new byte[16 + 20];
        rs.hash3.DoFinal(md5andsha1, 0);
              byte[] data = clientSigner.CalculateRawSignature(clientPrivateKey, md5andsha1);

        MemoryStream bos = new MemoryStream();
        TlsUtilities.WriteUint8(HP_CERTIFICATE_VERIFY, bos);
        TlsUtilities.WriteUint24(data.Length + 2, bos);
        TlsUtilities.WriteOpaque16(data, bos);
        byte[] message = bos.ToArray();

        rs.WriteMessage(RL_HANDSHAKE, message, 0, message.Length);
      }
      catch (CryptoException)
      {
        this.FailWithError(AL_fatal, AP_handshake_failure);
      }
    }
    
    /// <summary>Connects to the remote system.</summary>
    /// <param name="verifyer">Will be used when a certificate is received to verify
    /// that this certificate is accepted by the client.</param>
    /// <exception cref="IOException">If handshake was not successful</exception>
    public virtual void Connect(
      ICertificateVerifyer verifyer)
    {
          this.Connect(verifyer, null, null);
      }

    /// <summary>Connects to the remote system using client authentication</summary>
    /// <param name="verifyer">Will be used when a certificate is received to verify
    /// that this certificate is accepted by the client.</param>
    /// <param name="clientCertificate">The client's certificate to be provided to
    /// the remote system.</param>
    /// <param name="clientPrivateKey">The client's private key for the certificate
    /// to authenticate to the remote system (RSA or DSA).</param>
    // TODO Make public to enable client certificate support
    internal virtual void Connect(
      ICertificateVerifyer  verifyer,
      Certificate        clientCertificate,
      AsymmetricKeyParameter  clientPrivateKey)
    {
      if (clientCertificate == null)
      {
        clientCertificate = new Certificate(new X509CertificateStructure[0]);
      }

      if (clientPrivateKey == null)
      {
        if (clientCertificate.certs.Length != 0)
        {
          throw new ArgumentException("key not specified for certificate", "clientPrivateKey");
        }
      }
      else
      {
              if (clientCertificate.certs.Length == 0)
              {
          throw new ArgumentException("key specified without certificate", "clientPrivateKey");
              }
        else if (!clientPrivateKey.IsPrivate)
        {
          throw new ArgumentException("must be private", "clientPrivateKey");
        }
        else if (clientPrivateKey is RsaKeyParameters)
        {
          clientSigner = new TlsRsaSigner();
        }
        else if (clientPrivateKey is DsaPrivateKeyParameters)
        {
          clientSigner = new TlsDssSigner();
        }
        else
        {
          throw new ArgumentException("type not supported", "clientPrivateKey");
        }
      }

      this.verifyer = verifyer;
      this.clientCert = clientCertificate;
      this.clientPrivateKey = clientPrivateKey;

      /*
      * Send Client hello
      *
      * First, generate some random data.
      */
      this.clientRandom = new byte[32];

      /*
      * TLS 1.0 requires a unix-timestamp in the first 4 bytes
      */
      int t = (int)(DateTimeUtilities.CurrentUnixMs() / 1000L);
      this.clientRandom[0] = (byte)(t >> 24);
      this.clientRandom[1] = (byte)(t >> 16);
      this.clientRandom[2] = (byte)(t >> 8);
      this.clientRandom[3] = (byte)t;

      random.NextBytes(this.clientRandom, 4, 28);


      MemoryStream outStr = new MemoryStream();
      TlsUtilities.WriteVersion(outStr);
      outStr.Write(this.clientRandom, 0, this.clientRandom.Length);

      /*
      * Length of Session id
      */
      TlsUtilities.WriteUint8((short)0, outStr);

      /*
      * Cipher suites
      */
      TlsCipherSuiteManager.WriteCipherSuites(outStr);

      /*
      * Compression methods, just the null method.
      */
      byte[] compressionMethods = new byte[]{0x00};
      TlsUtilities.WriteOpaque8(compressionMethods, outStr);

      /*
      * Extensions
      */
      // TODO Collect extensions from client
      // Int32 -> byte[]
      Hashtable clientExtensions = new Hashtable();

      // TODO[SRP]
//      {
//        MemoryStream srpData = new MemoryStream();
//        TlsUtilities.WriteOpaque8(SRP_identity, srpData);
//
//        // TODO[SRP] RFC5054 2.8.1: ExtensionType.srp = 12
//        clientExtensions[12] = srpData.ToArray();
//      }

      this.extendedClientHello = (clientExtensions.Count > 0);

      if (extendedClientHello)
      {
        MemoryStream ext = new MemoryStream();

        foreach (int extType in clientExtensions.Keys)
        {
          byte[] extValue = (byte[])clientExtensions[extType];

          TlsUtilities.WriteUint16(extType, ext);
          TlsUtilities.WriteOpaque16(extValue, ext);
        }

        TlsUtilities.WriteOpaque16(ext.ToArray(), outStr);
      }

      MemoryStream bos = new MemoryStream();
      TlsUtilities.WriteUint8(HP_CLIENT_HELLO, bos);
      TlsUtilities.WriteUint24((int) outStr.Length, bos);
      byte[] outBytes = outStr.ToArray();
      bos.Write(outBytes, 0, outBytes.Length);
      byte[] message = bos.ToArray();
      rs.WriteMessage(RL_HANDSHAKE, message, 0, message.Length);
      connection_state = CS_CLIENT_HELLO_SEND;

      /*
      * We will now read data, until we have completed the handshake.
      */
      while (connection_state != CS_DONE)
      {
        rs.ReadData();
      }

      this.tlsInputStream = new TlsInputStream(this);
      this.tlsOutputStream = new TlsOuputStream(this);
    }

    /**
    * Read data from the network. The method will return immed, if there is
    * still some data left in the buffer, or block untill some application
    * data has been read from the network.
    *
    * @param buf    The buffer where the data will be copied to.
    * @param offset The position where the data will be placed in the buffer.
    * @param len    The maximum number of bytes to read.
    * @return The number of bytes read.
    * @throws IOException If something goes wrong during reading data.
    */
    internal int ReadApplicationData(byte[] buf, int offset, int len)
    {
      while (applicationDataQueue.Available == 0)
      {
        if (this.closed)
        {
          /*
          * We need to read some data.
          */
          if (this.failedWithError)
          {
            /*
            * Something went terribly wrong, we should throw an IOException
            */
            throw new IOException(TLS_ERROR_MESSAGE);
          }

          /*
          * Connection has been closed, there is no more data to read.
          */
          return 0;
        }

        try
        {
          rs.ReadData();
        }
        catch (IOException e)
        {
          if (!this.closed)
          {
            this.FailWithError(AL_fatal, AP_internal_error);
          }
          throw e;
        }
        catch (Exception e)
        {
          if (!this.closed)
          {
            this.FailWithError(AL_fatal, AP_internal_error);
          }
          throw e;
        }
      }
      len = System.Math.Min(len, applicationDataQueue.Available);
      applicationDataQueue.Read(buf, offset, len, 0);
      applicationDataQueue.RemoveData(len);
      return len;
    }

    /**
    * Send some application data to the remote system.
    * <p/>
    * The method will handle fragmentation internally.
    *
    * @param buf    The buffer with the data.
    * @param offset The position in the buffer where the data is placed.
    * @param len    The length of the data.
    * @throws IOException If something goes wrong during sending.
    */
    internal void WriteData(byte[] buf, int offset, int len)
    {
      if (this.closed)
      {
        if (this.failedWithError)
          throw new IOException(TLS_ERROR_MESSAGE);

        throw new IOException("Sorry, connection has been closed, you cannot write more data");
      }

      /*
      * Protect against known IV attack!
      *
      * DO NOT REMOVE THIS LINE, EXCEPT YOU KNOW EXACTLY WHAT
      * YOU ARE DOING HERE.
      */
      rs.WriteMessage(RL_APPLICATION_DATA, emptybuf, 0, 0);

      do
      {
        /*
        * We are only allowed to write fragments up to 2^14 bytes.
        */
        int toWrite = System.Math.Min(len, 1 << 14);

        try
        {
          rs.WriteMessage(RL_APPLICATION_DATA, buf, offset, toWrite);
        }
        catch (IOException e)
        {
          if (!closed)
          {
            this.FailWithError(AL_fatal, AP_internal_error);
          }
          throw e;
        }
        catch (Exception e)
        {
          if (!closed)
          {
            this.FailWithError(AL_fatal, AP_internal_error);
          }
          throw e;
        }

        offset += toWrite;
        len -= toWrite;
      }
      while (len > 0);
    }

    [Obsolete("Use 'OutputStream' property instead")]
    public TlsOuputStream TlsOuputStream
    {
      get { return this.tlsOutputStream; }
    }

    /// <summary>A Stream which can be used to send data.</summary>
    public virtual Stream OutputStream
    {
      get { return this.tlsOutputStream; }
    }

    [Obsolete("Use 'InputStream' property instead")]
    public TlsInputStream TlsInputStream
    {
      get { return this.tlsInputStream; }
    }

    /// <summary>A Stream which can be used to read data.</summary>
    public virtual Stream InputStream
    {
      get { return this.tlsInputStream; }
    }

    /**
    * Terminate this connection with an alert.
    * <p/>
    * Can be used for normal closure too.
    *
    * @param alertLevel       The level of the alert, an be AL_fatal or AL_warning.
    * @param alertDescription The exact alert message.
    * @throws IOException If alert was fatal.
    */
    internal void FailWithError(
      short  alertLevel,
      short  alertDescription)
    {
      /*
      * Check if the connection is still open.
      */
      if (!closed)
      {
        /*
        * Prepare the message
        */
        byte[] error = new byte[2];
        error[0] = (byte)alertLevel;
        error[1] = (byte)alertDescription;
        this.closed = true;

        if (alertLevel == AL_fatal)
        {
          /*
          * This is a fatal message.
          */
          this.failedWithError = true;
        }
        rs.WriteMessage(RL_ALERT, error, 0, 2);
        rs.Close();
        if (alertLevel == AL_fatal)
        {
          throw new IOException(TLS_ERROR_MESSAGE);
        }
      }
      else
      {
        throw new IOException(TLS_ERROR_MESSAGE);
      }
    }

    /// <summary>Closes this connection</summary>
    /// <exception cref="IOException">If something goes wrong during closing.</exception>
    public virtual void Close()
    {
      if (!closed)
      {
        this.FailWithError((short)1, (short)0);
      }
    }

    /**
    * Make sure the Stream is now empty. Fail otherwise.
    *
    * @param is The Stream to check.
    * @throws IOException If is is not empty.
    */
    internal void AssertEmpty(
      MemoryStream inStr)
    {
      if (inStr.Position < inStr.Length)
      {
        this.FailWithError(AL_fatal, AP_decode_error);
      }
    }

    internal void Flush()
    {
      rs.Flush();
    }
  }
}
www.java2v.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.