Input/Output

yaSSL uses a simulated streaming input/output system defined in buffer.hpp. The buffers behave like a smart c style array for use with overloaded << and >> and provide a checking option, input is designed to be read from and output is for writing. std::vector is not used because of a desire to have checked []access, offset, and the ability to read/write to the buffer bulk wise while maintaining the correct size and offset. The buffers themselves use a checking policy.

The checking policy is Check by default but may be turned off at compile time to use NoCheck. Check merely ensures that range errors are caught and is especially useful for debugging and testing, though it is a simple inlined test, some users may prefer to avoid runtime buffer flow checks.

One other feature worth noting about the buffers is that since they know their current offset, an index is not required for operator[]. By passing the constant AUTO, the user is freed from making silly index tracking errors, easing the burden of simple, but still error-prone, input/output programming. For example, compare the following two implementations:

     A) // input operator for ServerHello
     input_buffer& operator>>(input_buffer& input, ServerHello& hello)
     {
          // Protocol
          hello.server_version_.major_ = input[AUTO];
          hello.server_version_.minor_ = input[AUTO];

          // Random
          input.read(hello.random_, RAN_LEN);

          // Session
          hello.id_len_ = input[AUTO];
          input.read(hello.session_id_, ID_LEN);

          // Suites
          hello.cipher_suite_[0] = input[AUTO];
          hello.cipher_suite_[1] = input[AUTO];

          // Compression
          hello.compression_method_ = CompressionMethod(input[AUTO]);

          return input;
     }

     B) // input operator for ServerHello
     input_buffer& operator>>(input_buffer& input, ServerHello& hello)
     {
          size_t i = input.get_current();

          // Protocol
          hello.server_version_.major_ = input[i++];
          hello.server_version_.minor_ = input[i++];

          // Random
          input.read(hello.random_, RAN_LEN);
          i += RAN_LEN;

          // Session
          hello.id_len_ = input[i++];
          input.read(hello.session_id_, ID_LEN);
          i += ID_LEN;

          // Suites
          hello.cipher_suite_[0] = input[i++];
          hello.cipher_suite_[1] = input[i++];

          // Compression
          hello.compression_method_ =
          CompressionMethod(input[i++]);

          input.set_current(i);

          return input;
     }

While B is not much more difficult to implement, the chances for simple errors to occur are increased. Not to mention having to remember to get/set the current offset before passing the buffer to handlers and in the event of exceptions, there is no guarantee that the index is correctly positioned, making recovery nearly impossible.

Factories

Factories are used in several areas by yaSSL. Many of the structures defined in the SSL standard obey the Liskov Substitution Principle (Functions that use pointers/references to base classes must be able to use objects of derived classes transparently). That is, a ServerHello message IS A handshake message and a Finished message IS also A handshake message. Moreover, objects of the derived classes need to be created at runtime based on the negotiated parameters of the connection and message types. For example, when a message header is read, a type field identifies the message that follows. Instead of using a switch statement that becomes increasingly harder to maintain, yaSSL uses a message factory to create the desired object.


     Message* msg = MessageFactory.CreateObject(hdr.type_);

factory.hpp defines the generic implementation like this:


     template
     <
          class AbstractProduct,
          typename IdentifierType = int,
          typename ProductCreator = AbstractProduct* (*)()
     >
     class Factory {
          typedef std::map CallBackMap;
          CallBackMap callbacks_;
     ...}

The Message Factory instance is created like this:


     typedef Factory MessageFactory;

For more information on factories please see the design pattern discussion in (GoF) and Alexandrescu's chapter in Modern C++ Design.

Policies

Cryptographic Policies are employed to simply yaSSL's use of digests, ciphers, and signature systems. Each is briefly described and defined in crypto_wrapper.hpp.

Digests, or MACs (Message Authentication Codes) use a basic policy that hides the underlying implementation:


     struct MAC {
          virtual void get_digest(byte*) = 0;
          virtual void get_digest(byte*, const byte*, unsigned int) = 0;
          virtual void update(const byte*, unsigned int) = 0;
     ...}

Really only the first get_digest() and update() are needed but the extended version of get_digest() allows a user to both update and retrieve the digest in the same call, simplifying some operations. MD5 and SHA use the policy in their definitions, here is SHA as an example:


     class SHA : public MAC {
     public:
          void get_digest(byte*);
          void get_digest(byte*, const byte*, unsigned int);
          void update(const byte*, unsigned int);
     ...}

So when yaSSL has a MAC pointer, it uses it without knowledge of the derived object actually being used, conforming to the Liskov Substitution Principle.

Ciphers also employ a basic policy:


     struct BulkCipher {
          virtual void encrypt(byte*, const byte*, unsigned int) = 0;
          virtual void decrypt(byte*, const byte*, unsigned int) = 0;
          virtual void set_encryptKey(const byte*, const byte* = 0) = 0;
          virtual void set_decryptKey(const byte*, const byte* = 0) = 0;
     ...}

These functions are all necessary and yaSSL uses BulkCipher pointers transapernetly and without knowledge of whether the actual object is DES, 3DES, or RC4.

Authentication policies define the signature verification interface used by yaSSL.


     struct Auth {
          virtual void sign(byte*, const byte*, unsigned int, const RandomPool&) =0;
          virtual bool verify(const byte*, unsigned int, const byte*, unsigned int) = 0;
     ...}

The authentication policy is straight forward and support for DSS and RSA is built into yaSSL.

Overall

Overall Design. (Reading/Writing SSL Messages) The combination of yaSSL's buffer strategy and factory use provides a simple paradigm used throughout the handshake and message layer implementations. Reading and processing an input message could not be simpler:


     while(!buffer.eof()) {
          // each record
          RecordLayerHeader hdr;
          buffer >> hdr;

          while (buffer.get_current() < hdr.length_ + offset) {
               // each message in record
               std::auto_ptr msg(mf.CreateObject(hdr.type_));
               buffer >> *msg;
               msg->Process(buffer, ssl);
          }
          offset += hdr.length_
     }

This same loop is used by both clients and servers and efficiently handles all message types. Handshake processing uses the exact same paradigm. Sending an SSL output messages in yaSSL is primarily the responsibility output buffer and some simple helper functions like buildHeader() and buildOutput(). Their implementations are also simple.


     void buildHeader(SSL& ssl, RecordLayerHeader& rlHeader, const Message& msg)
     {
          ProtocolVersion pv = ssl.get_connection().version_;
          rlHeader.type_ = msg.get_type();
          rlHeader.version_.major_ = pv.major_;
          rlHeader.version_.minor_ = pv.minor_;
          rlHeader.length_ = msg.get_length();
     }

     void buildOutput(output_buffer& buffer, const RecordLayerHeader& rlHdr,
                                   const HandShakeHeader& hsHdr, const HandShakeBase& shake)
     {
          buffer.allocate(RECORD_HEADER + rlHdr.length_);
          buffer << rlHdr << hsHdr << shake;
     }

In fact, using the above functions and a couple of other helpers reveal the ease with which yaSSL sends a client hello message:

     void sendClientHello(SSL& ssl)
     {
          ClientHello ch;
          RecordLayerHeader rlHeader;
          HandShakeHeader hsHeader;
          output_buffer out;

          buildClientHello(ssl, ch);
          ssl.set_random(ch.get_random(), client_end);
          buildHeaders(ssl, hsHeader, rlHeader, ch);
          buildOutput(out, rlHeader, hsHeader, ch);
          hashHandShake(ssl, out);

          ssl.get_socket().send(out.get_buffer(), out.get_size());
     }

Please see handshake.cpp and yassl_imp.cpp for a better understanding of how yaSSL sends and retrieves the other messages and handshake types.

Home