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.
factory.hpp defines the generic implementation like this:
The Message Factory instance is created like this:
For more information on factories please see the design pattern discussion in (GoF) and Alexandrescu's chapter in Modern C++ Design.
Digests, or MACs (Message Authentication Codes) use a basic policy that hides the underlying implementation:
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:
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:
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.
The authentication policy is straight forward and support for DSS and RSA is built into yaSSL.
while (buffer.get_current() < hdr.length_ + offset) {
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 buildOutput(output_buffer& buffer, const RecordLayerHeader& rlHdr,
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)
buildClientHello(ssl, ch);
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.
// each message in record
std::auto_ptr
buffer >> *msg;
msg->Process(buffer, ssl);
}
offset += hdr.length_
}
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();
}
const HandShakeHeader& hsHdr, const HandShakeBase& shake)
{
buffer.allocate(RECORD_HEADER + rlHdr.length_);
buffer << rlHdr << hsHdr << shake;
}
{
ClientHello ch;
RecordLayerHeader rlHeader;
HandShakeHeader hsHeader;
output_buffer out;
ssl.set_random(ch.get_random(), client_end);
buildHeaders(ssl, hsHeader, rlHeader, ch);
buildOutput(out, rlHeader, hsHeader, ch);
hashHandShake(ssl, out);
}