almost finished documenting, moved SSLSession implementation into it's own file, fixed infinite loop when overflowing m_iobuf

This commit is contained in:
Noah Laptop 2019-03-31 12:55:58 -07:00
parent cd94d0bf3b
commit 648104c7e3
8 changed files with 265 additions and 250 deletions

View file

@ -26,7 +26,7 @@ Where:
For example, if I am using EthernetClient, a generated array of 2 trust anchors, and the analog pin A7, I would declare an SSLClient instance using: For example, if I am using EthernetClient, a generated array of 2 trust anchors, and the analog pin A7, I would declare an SSLClient instance using:
```C++ ```C++
SSLClient<EthernetClient> client(EthernetClient(), TAs, (size_t)2, A7); SSLClient<EthernetClient> client(EthernetClient(), TAs, 2, A7);
``` ```
Once that is setup, simply use SSLClient as you would the base client class: Once that is setup, simply use SSLClient as you would the base client class:
```C++ ```C++
@ -49,22 +49,79 @@ For more information on SSLClient, check out the [examples](./examples), [API do
## How It Works ## How It Works
SSLClient was created to integrate SSL seamlessly with the Arduino infrastructure, and so it does just that: implementing the brilliant [BearSSL](link-here) as a proxy in front of any Arduino socket library. BearSSL is designed with low flash footprint in mind, and as a result does little verification of improper programming, relying on the developer to ensure the code is correct. Since SSLClient is built specifically for the Arduino ecosystem, most of the code adds those programming checks back in, helping debugging be a fast and simple process. The rest manages the state of BearSSL, and ensures a manageable memory footprint. SSLClient was created to integrate SSL seamlessly with the Arduino infrastructure, and so it does just that: implementing the brilliant [BearSSL](https://bearssl.org/) as a proxy in front of any Arduino socket library. BearSSL is designed with low flash footprint in mind, and as a result does little verification of improper programming, relying on the developer to ensure the code is correct. Since SSLClient is built specifically for the Arduino ecosystem, most of the code adds those programming checks back in, helping debugging be a fast and simple process. The rest manages the state of BearSSL, and ensures a manageable memory footprint.
## Implementation Notes Additionally, the bulk of SSLClient is split into two components: a template class [SSLClient](./src/SSLClient.h), and an implementation class [SSLClientImpl](./src/SSLClientImpl.h). The template class serves to abstract some functions not implemented in the Arduino Client interface (such as `EthernetClient::remoteIP`), and the implementation class is the rest of the SSLClient library.
## Other Features
### Logging
SSLClient also allows for changing the debugging level by adding an additional parameter to the constructor:
```C++
SSLClient<EthernetClient> client(EthernetClient(), TAs, (size_t)2, A7, SSL_INFO);
```
Logging is always outputted through the [Arduino Serial interface](https://www.arduino.cc/reference/en/language/functions/communication/serial/), so you'll need to setup Serial before you can view the SSL logs. Log levels are enumerated in [Error](./src/SSLClientImpl.h). The log level is set to `SSL_WARN` by default.
### Errors
When SSLClient encounters an error, it will attempt to terminate the SSL session gracefully if possible, and then close the socket. Simple error information can be found from `SSLClient::getWriteError()`, which will return a value from [this enumeration](link-me). For more detailed diagnostics, you can look at the serial logs, which will be displayed if the log level is at `SSL_ERROR` or lower.
### Write Buffering
As you may have noticed in the documentation for [SSLClient::write](link-me), calling this function does not actually write to the network. Instead, you must call [SSLClient::available](link-me) or [SSLClient::flush](link-me), which will detect that the buffer is ready and write to the network (see [SSLClient::write](link-me) for details).
This was implemented as a buffered function because examples in Arduino libraries will often write to the network like so:
```C++
EthernetClient client;
// ...
client.println("GET /asciilogo.txt HTTP/1.1");
client.println("Host: arduino.cc");
client.println("Connection: close");
while (!client.available()) { /* ... */ }
// ...
```
This is fine with most network clients. With SSL, however, if we are encrypting and writing to the network every write() call this will result in a lot of small encryption tasks. Encryption takes a lot of time and code, and so to reduce the overhead of an SSL connection SSLClient::write implicitly buffers until the developer states that they are waiting for data to be received with SSLClient::available.
If you would like to trigger a network write manually without using the SSLClient::available, you can also call SSLClient::flush, which will write all data and return when finished.
### Session Caching
As detailed in the [resources section](#resources), SSL handshakes take an extended period (1-4sec) to negotiate. To remedy this problem, BearSSL is able to keep a [SSL session cache](https://bearssl.org/api1.html#session-cache) of the clients it has connected to. If BearSSL successfully resumes an SSL session, it can reduce connection time to 100-500ms.
In order to use SSL session resumption:
* The website you are connecting to must support it. Support is widespread, but you can verify easily using the [SSLLabs tool](https://www.ssllabs.com/ssltest/).
* you must reuse the same SSLClient object (SSL Sessions are stored in the object itself).
* you must reconnect to the exact same server.
SSLClient automatically stores an IP address and hostname in each session, ensuring that if you call `connect("www.google.com")` SSLClient will use a IP address that recognizes the SSL session instead of another IP address associated with `"www.google.com"`. Because some websites have multiple servers on a single IP address (github.com is an example), however, you may find that even if you are connecting to the same host the connection does not resume. This is a flaw in the SSL session protocol, and has been resolved in future versions. SSL sessions can also expire based on server criteria, which will result in a standard 4-10 second connection.
You can test whether or not a website can resume SSL Sessions using the [Session Example](./examples/Session_Example/Session_Example.ino) included with this library. Because of all the confounding factors of SSL Sessions, it is generally prudent while programming to assume the session will always fail to resume.
## Implementation Gotchas
Some ideas that didn't quite fit in the API documentation. Some ideas that didn't quite fit in the API documentation.
### Certificate Verification ### Certificate Verification
SSLClient uses BearSSL's [minimal x509 verification engine](link-me) to verify the certificate of an SSL connection. This engine requires the developer create a trust anchor array using values stored in trusted root certificates. Check out [this document](./TrustAnchors.md) for more details on this component of SSLClient. SSLClient uses BearSSL's [minimal x509 verification engine](https://bearssl.org/x509.html#the-minimal-engine) to verify the certificate of an SSL connection. This engine requires the developer create a trust anchor array using values stored in trusted root certificates. Check out [this document](./TrustAnchors.md) for more details on this component of SSLClient.
### Session Caching BearSSL also features a [known certificate validation engine](https://bearssl.org/x509.html#the-known-key-engine), which only allows for a single domain in exchange for a significantly reduced resource usage (flash and CPU time). This functionality is planned to be implemented in the future.
### Resources
The SSL protocol recommends a device support many different encryption algorithms, as well as protocols for SSL itself. The complexity of both of those components results in many medium sized components forming an extremely large whole. Additionally, most embedded processors lack the sophisticated math hardware commonly found in a modern CPU, and as a result require more instructions to create the encryption algorithms SSL requires. This not only increases size but makes the algorithms slow and memory intensive.
To illustrate this, I will run some tests on various domains below. I haven't yet, but I will.
If flash footprint is becoming a problem, there are numerous debugging strings (~3kb estimated) that can be removed from `SSLClient.h`, `SSLClientImpl.h`, and `SSLClientImpl.cpp`. I have not figured out a way to configure compilation of these strings, so you will need to modify the library to remove them yourself.
### Read Buffer Overflow
SSL is a buffered protocol, and since most microcontrollers have limited resources (see [Resources](#resources)), SSLClient is limited in the size of its buffers. A common problem I encountered with SSL connections is buffer overflow, caused by the server sending too much data at once. This problem is caused by the microcontroller being unable to copy and decrypt data faster than it is being received, forcing some data to be discarded. This usually puts BearSSL in an unrecoverable state, forcing SSLClient to close the connection with a write error. If you are experiencing frequent timeout problems, this could be the reason why.
In order to remedy this problem, the device must be able to read the data faster than it is being received, or alternatively have a cache large enough to store the entire payload. Since SSL's encryption forces the device to read slowly, this means we must increase the cache size. Depending on your platform, there are a number of ways this can be done:
* Sometimes your communication shield will have an internal buffer, which can be expanded through the driver code. This is the case with the Arduino Ethernet library (in the form of the MAX_SOCK_NUM and ETHERNET_LARGE_BUFFERS macros), however the library must be modified for the change to take effect.
* SSLClient has an internal buffer SSLClientImpl::m_iobuf, which can be expanded. BearSSL limits the amount of data that can be processed based on the stage in the SSL handshake, and so this will change will have limited usefulness.
* In some cases, a website will send so much data that even with the above solutions SSLClient will be unable to keep up (a website with a lot of HTML is an example). In these cases you will have to find another method of retrieving the data you need.
* If none of the above are viable, it is possible to implement your own Client class which has an internal buffer much larger than both the driver and BearSSL. This would require in-depth knowledge of programming and the communication shield you are working with, as well as a microcontroller with a significant amount of RAM.
### Cipher Support ### Cipher Support
SSLClient supports only TLS1.2 and the ciphers listed in [this file](./src/TLS12_only_profile) under `suites[]` by default, and the list is relatively small to keep the connection secure and the flash footprint down. These ciphers should work for most applications, however if for some reason you would like to use an older version of TLS or a different cipher, you can change the BearSSL profile being used by SSLClient to an [alternate one with support for older protocols](./src/bearssl/src/ssl). To do this, edit `SSLClientImpl::SSLClientImpl` to change these lines:
SSLClient supports only TLS1.2 and the ciphers listed in [this file under `suites[]`](./src/TLS12_only_profile) by default, and the list is relatively small to keep the connection secure and the flash footprint down. These ciphers should work for most applications, however if for some reason you would like to use an older version of TLS or a different cipher, you can change the BearSSL profile being used by SSLClient to an [alternate one with support for older protocols](./src/bearssl/src/ssl). To do this, edit `SSLClientImpl::SSLClientImpl` to change these lines:
```C++ ```C++
br_client_init_TLS12_only(&m_sslctx, &m_x509ctx, m_trust_anchors, m_trust_anchors_num); br_client_init_TLS12_only(&m_sslctx, &m_x509ctx, m_trust_anchors, m_trust_anchors_num);
// comment the above line and uncomment the line below if you're having trouble connecting over SSL // comment the above line and uncomment the line below if you're having trouble connecting over SSL
@ -76,19 +133,4 @@ to this:
// comment the above line and uncomment the line below if you're having trouble connecting over SSL // comment the above line and uncomment the line below if you're having trouble connecting over SSL
br_ssl_client_init_full(&m_sslctx, &m_x509ctx, m_trust_anchors, m_trust_anchors_num); br_ssl_client_init_full(&m_sslctx, &m_x509ctx, m_trust_anchors, m_trust_anchors_num);
``` ```
If for some unfortunate reason you need SSL 3.0 or SSL 2.0, you will need to modify the BearSSL profile to enable support. Check out the [BearSSL Documentation](link-me) and I wish you the best of luck. If for some unfortunate reason you need SSL 3.0 or SSL 2.0, you will need to modify the BearSSL profile to enable support. Check out the [BearSSL profiles documentation](https://bearssl.org/api1.html#profiles) and I wish you the best of luck.
### Resources
The SSL protocol recommends a device support many different encryption algorithms, as well as protocols for SSL itself. The complexity of both of those components results in many medium sized components forming an extremely large whole. Additionally, most embedded processors lack the sophisticated math hardware commonly found in a modern CPU, and as a result require more instructions to create the encryption algorithms SSL requires. This not only increases size but makes the algorithms slow and memory intensive.
To illustrate this, I will run some tests on various domains below. I haven't yet, but I will.
If flash footprint is becoming a problem, there are numerous debugging strings (~3kb estimated) that can be removed from `SSLClient.h`, `SSLClientImpl.h`, and `SSLClientImpl.cpp`. I have not figured out a way to configure compilation of these strings, so you will need to modify the library to remove them yourself.
### Logging
SSLClient also allows for changing the debugging level by adding an additional parameter to the constructor:
```C++
SSLClient<EthernetClient> client(EthernetClient(), TAs, (size_t)2, A7, SSL_INFO);
```
Logging is always outputted through the [Arduino Serial interface](https://www.arduino.cc/reference/en/language/functions/communication/serial/), so you'll need to setup Serial before you can view the SSL logs. Log levels are enumerated in [Error](./src/SSLClientImpl.h). The log level is set to `SSL_WARN` by default.

View file

@ -0,0 +1,61 @@
# Trust Anchors
SSLClient uses BearSSL's [minimal x509 verification engine](https://bearssl.org/x509.html#the-minimal-engine) to verify the certificate of an SSL connection. This engine requires the developer create a trust anchor array using values stored in trusted root certificates. In short, these trust anchor arrays allow BearSSL to verify that the server being connected to is who they say they are, and not someone malicious. You can read more about certificates and why they are important [here](https://www.globalsign.com/en/ssl-information-center/what-is-an-ssl-certificate/).
SSLClient stores trust anchors in hardcoded constant variables, passed into `SSLClient::SSLClient` during setup. These constants are generally stored in their own header file as found in [the BearSSL docs](https://bearssl.org/api1.html#profiles). This header file will look something like:
```C++
#define TAs_NUM 1
static const unsigned char TA_DN0[] = {
// lots of raw bytes here
// ...
};
static const unsigned char TA_RSA_N0[] = {
// lots of raw bytes here
//...
};
static const unsigned char TA_RSA_E0[] = {
// 1-3 bytes here
};
static const br_x509_trust_anchor TAs[] = {
{
{ (unsigned char *)TA_DN0, sizeof TA_DN0 },
BR_X509_TA_CA,
{
BR_KEYTYPE_RSA,
{ .rsa = {
(unsigned char *)TA_RSA_N0, sizeof TA_RSA_N0,
(unsigned char *)TA_RSA_E0, sizeof TA_RSA_E0,
} }
}
},
};
```
A full example of a trust anchor header can be found in [this file](./readme/cert.h). Full documentation for the format of these variables can be found in the [BearSSL documentation for br_x509_trust_anchor](https://bearssl.org/apidoc/structbr__x509__trust__anchor.html).
## Generating Trust Anchors
### HTTPS
For HTTPS, there a couple of tools you can use. Ordered from easy to hard:
* [This website, written to simplify the creation of trust anchor headers](https://openslab-osu.github.io/bearssl-certificate-utility/). Simply plug and play.
* [pycert_bearssl](./tools/pycert_bearssl/pycert_bearssl.py), a command line utility based on a [pycert](https://learn.adafruit.com/introducing-the-adafruit-wiced-feather-wifi/pycert-dot-py). You will need to install Python 3, and follow the instructions in the [pycert_bearssl.py file](./tools/pycert_bearssl/pycert_bearssl.py). You'll want to use the `pycert_bearssl.py download` command once the utility is set up.
* The brssl command line utility, included in the [BearSSL source](https://bearssl.org/gitweb/?p=BearSSL;a=blob_plain;f=tools/brssl.h;hb=HEAD). You will need to compile this file yourself.
### Other Connections
For other kinds of SSL connections, you will need to find the root certificate being used by your host. You can check out [this StackExchange post](https://superuser.com/questions/97201/how-to-save-a-remote-server-ssl-certificate-locally-as-a-file) for numerous methods of acquiring this certificate from a server. If these methods are not sufficient, you may need to request this certificate from your network administrator. Once you have the certificate, convert it to PEM format if needed (I use [this website](https://www.sslshopper.com/ssl-converter.html)), and use the `pycert_bearssl.py convert` command to convert the certificate into a trust anchor header.
## Using Trust Anchors
Once you've generated a trust anchor array, add it to your Arduino sketch using the `Sketch->Add File` button in the Arduino IDE, and link it to your SSLClient like so:
```C++
#include "yourtrustanchorfile.h"
// ...
SSLClient<SomeClientType> client(SomeClient, TAs, (size_t)TAs_NUM, SomePin);
// ...
```
Where `yourtrustanchorfile.h` contains a generated trust anchor array names `TAs`, with length `TAs_NUM`. BearSSL will now automatically use these trust anchors when `SSLClient::connect` is called.

View file

@ -18,20 +18,6 @@
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
/**
* SSLCLient.h
*
* This library was created to provide SSL functionality to the {@link https://learn.adafruit.com/adafruit-wiz5500-wiznet-ethernet-featherwing/overview}
* Adafruit Ethernet shield. Since this shield does not implement SSL functionality on
* its own, we need to use an external library: in this case BearSSL {@link https://bearssl.org/},
* which is also used in the Arduino ESP8266 core. SSLClient will serve to implement the
* BearSSL functionality inbetween EthernetClient and the User, such that the user.
*
* This file specifically controls the class templating used to allow SSLClient to interface
* with all of the CLient-based classes. To see details on the implementations of the functions
* in SSLClient, please see {@link ./SSLClientImpl.h}.
*/
#include <type_traits> #include <type_traits>
#include "Client.h" #include "Client.h"
#include "SSLClientImpl.h" #include "SSLClientImpl.h"
@ -41,27 +27,13 @@
#define SSLClient_H_ #define SSLClient_H_
/** /**
* \brief The main SSLClient class * @brief The main SSLClient class
* * Check out README.md for more info.
* TODO: fix this blurb
*
* This class serves as a templating proxy class for the SSLClientImpl to do the real work.
*
* A problem arose when writing this class: I wanted the user to be able to construct
* this class in a single line of code (e.g. SSLClient(EthernetClient())), but I also
* wanted to avoid the use of dynamic memory if possible. In an attempt to solve this
* problem I used a templated classes. However, becuase of the Arduino build process
* this meant that the implementations for all the functions had to be in a header
* file (a weird effect of using templated classes and linking) which would slow down
* the build quite a bit. As a comprimise, I instead decided to build the main class (SSLCLient)
* as a templated class, and have use a not templated implementation class (SSLClientImpl)
* that would be able to reside in a seperate file. This gets the best of both worlds
* from the client side, however from the developer side it can be a bit confusing.
*/ */
template <class C, size_t SessionCache = 1> template <class C, size_t SessionCache = 1>
class SSLClient : public SSLClientImpl { class SSLClient : public SSLClientImpl {
/** /*
* static checks * static checks
* I'm a java developer, so I want to ensure that my inheritance is safe. * I'm a java developer, so I want to ensure that my inheritance is safe.
* These checks ensure that all the functions we use on class C are * These checks ensure that all the functions we use on class C are
@ -81,22 +53,20 @@ static_assert(SessionCache <= 3, "You need to decrease the size of m_iobuf in or
public: public:
/** /**
* @brief copies the client object, and passes the various parameters to the SSLCLientImpl functions. * @brief Initialize SSLClient with all of the prerequisites needed.
*
* We copy the client because we aren't sure the Client object
* is going to exists past the inital creation of the SSLClient.
* *
* @pre You will need to generate an array of trust_anchors (root certificates) * @pre You will need to generate an array of trust_anchors (root certificates)
* based off of the domains you want to make SSL connections to. Check out the * based off of the domains you want to make SSL connections to. Check out the
* Wiki on the pycert-bearssl tool for a simple way to do this. * TrustAnchors.md file for more info.
* @pre The analog_pin should be set to input. * @pre The analog_pin should be set to input.
* *
* @param client The base network device to create an SSL socket on. This object will be copied
* and the copy will be stored in SSLClient.
* @param trust_anchors Trust anchors used in the verification * @param trust_anchors Trust anchors used in the verification
* of the SSL server certificate, generated using the `brssl` command * of the SSL server certificate. Check out TrustAnchors.md for more info.
* line utility. For more information see the samples or bearssl.org * @param trust_anchors_num The number of objects in the trust_anchors array.
* @param trust_anchors_num The number of trust anchors stored * @param analog_pin An analog pin to pull random bytes from, used in seeding the RNG.
* @param analog_pin An analog pin to pull random bytes from, used in seeding the RNG * @param debug whether to enable or disable debug logging.
* @param debug whether to enable or disable debug logging, must be constexpr
*/ */
explicit SSLClient(const C& client, const br_x509_trust_anchor *trust_anchors, const size_t trust_anchors_num, const int analog_pin, const DebugLevel debug = SSL_WARN) explicit SSLClient(const C& client, const br_x509_trust_anchor *trust_anchors, const size_t trust_anchors_num, const int analog_pin, const DebugLevel debug = SSL_WARN)
: SSLClientImpl(trust_anchors, trust_anchors_num, analog_pin, debug) : SSLClientImpl(trust_anchors, trust_anchors_num, analog_pin, debug)
@ -113,93 +83,81 @@ public:
//======================================== //========================================
/** /**
* @brief Connect over SSL to a host specified by an ip address * @brief Connect over SSL to a host specified by an IP address.
* *
* SSLClient::connect(host, port) should be preffered over this function, * SSLClient::connect(host, port) should be preferred over this function,
* as verifying the domain name is a step in ensuring the certificate is * as verifying the domain name is a step in ensuring the certificate is
* legitimate, which is important to the security of the device. Additionally, * legitimate, which is important to the security of the device. Additionally,
* SSL sessions cannot be resumed, which can drastically increase initial * SSL sessions cannot be resumed, which can drastically increase initial
* connect time. * connect time.
* *
* This function initializes EthernetClient by calling EthernetClient::connect * This function initializes the socket by calling m_client::connect(IPAddress, uint16_t)
* with the parameters supplied, then once the socket is open initializes * with the parameters supplied, then once the socket uses BearSSL to
* the appropriete bearssl contexts. Due to the design of the SSL standard, * to complete a SSL handshake. Due to the design of the SSL standard,
* this function will probably take an extended period (1-4sec) to negotiate * this function will probably take an extended period (1-4sec) to negotiate
* the handshake and finish the connection. This function runs until the SSL * the handshake and finish the connection. This function runs until the SSL
* handshake succeeds or fails, as found in most Arduino libraries. * handshake succeeds or fails.
* *
* The implementation for this function can be found in SSLClientImpl::connect_impl(IPAddress, uint16_t) * SSL requires the client to generate some random bits (to be later combined
* with some random bits from the server), so SSLClient uses the least significant
* bits from the analog pin supplied in the constructor. The random bits are generated
* from 16 consecutive analogReads, and given to BearSSL before the handshake
* starts.
* *
* @pre The underlying client object (passed in through the ctor) in a non- * The implementation for this function can be found in SSLClientImpl::connect_impl(IPAddress, uint16_t).
* error state, and must be able to access the server being connected to. *
* @pre SSLCLient can only have one connection at a time, so the client * @pre The underlying client object (passed in through the constructor) is in a non-
* object must not already have a socket open. * error state, and must be able to access the IP.
* @pre There must be sufficient memory availible on the device to verify * @pre SSLClient can only have one connection at a time, so the client
* object must not already be connected.
* @pre There must be sufficient memory available on the device to verify
* the certificate (if the free memory drops below 8000 bytes during certain * the certificate (if the free memory drops below 8000 bytes during certain
* points in the connection, SSLCLient will fail). * points in the connection, SSLClient will fail).
* @pre There must be a trust anchor given to the ctor that corresponds to * @pre There must be a trust anchor given to the constructor that corresponds to
* the certificate provided by the IP address being connected to. For more * the certificate provided by the IP address being connected to. For more
* information check out the wiki on the pycert-bearssl tool. * information check out TrustAnchors.md .
* @pre The analog pin passed to the ctor must be set to input, and must
* be wired to something sort of random (floating is fine).
* *
* @param ip The ip address to connect to * @param ip The IP address to connect to
* @param port the port to connect to * @param port the port to connect to
* @returns 1 if success, 0 if failure (as found in EthernetClient) * @returns 1 if success, 0 if failure
*/ */
virtual int connect(IPAddress ip, uint16_t port) { return connect_impl(ip, port); } virtual int connect(IPAddress ip, uint16_t port) { return connect_impl(ip, port); }
/** /**
* @brief Connect over SSL using connect(ip, port), using a DNS lookup to * @brief Connect over SSL to a host specified by a hostname.
* get the IP Address first.
* *
* This function initializes EthernetClient by calling EthernetClient::connect * This function initializes the socket by calling m_client::connect(const char*, uint16_t)
* with the parameters supplied, then once the socket is open uses BearSSL to * with the parameters supplied, then once the socket is open uses BearSSL to
* to complete a SSL handshake. This function runs until the SSL handshake * to complete a SSL handshake. This function runs until the SSL handshake
* succeeds or fails, as found in most Arduino libraries. * succeeds or fails.
* *
* SSL requires the client to generate some random bits (to be later combined * SSL requires the client to generate some random bits (to be later combined
* with some random bits from the server), so SSLClient uses the least signinigant * with some random bits from the server), so SSLClient uses the least significant
* bits from the analog pin supplied in the ctor. The random bits are generated * bits from the analog pin supplied in the constructor. The random bits are generated
* from 16 consecutive analogReads, and given to BearSSL before the handshake * from 16 consecutive analogReads, and given to BearSSL before the handshake
* starts. * starts.
* *
* Due to the design of the SSL standard, this function will probably take an * This function will usually take around 4-10 seconds. If possible, this function
* extended period (1-4sec) to negotiate the handshake and finish the * also attempts to resume the SSL session if one is present matching the hostname
* connection. Since the hostname is provided, however, BearSSL is able to keep * string, which will reduce connection time to 100-500ms. To read more about this
* a session cache of the clients we have connected to. This should reduce * functionality, check out Session Caching in the README.
* connection time to about 100-200ms. In order to use this feature, the website
* you are connecting to must support it (most do by default), you must
* reuse the same SSLClient object, and you must reconnect to the same server.
* SSLClient automatcally stores an IP address and hostname in each session,
* ensuring that if you call connect("www.google.com") SSLClient will use a
* cached IP address instead of another DNS lookup. Because some websites have
* multiple servers on a single IP address (github.com is an example), however,
* you may find that even if you are connecting to the same host the connection
* does not resume. This is a flaw in the SSL session protocol, and has been
* resolved in future versions. On top of all that, SSL sessions can expire
* based on server criteria, which will result in a regular connection time.
* Because of all these factors, it is generally prudent to assume the
* connection will not be resumed, and go from there.
* *
* The implementation for this function can be found in SSLClientImpl::connect_impl(const char*, uint16_t) * The implementation for this function can be found in SSLClientImpl::connect_impl(const char*, uint16_t)
* *
* @pre The underlying client object (passed in through the ctor) in a non- * @pre The underlying client object (passed in through the constructor) is in a non-
* error state, and must be able to access the server being connected to. * error state, and must be able to access the IP.
* @pre SSLCLient can only have one connection at a time, so the client * @pre SSLClient can only have one connection at a time, so the client
* object must not already have a socket open. * object must not already be connected.
* @pre There must be sufficient memory availible on the device to verify * @pre There must be sufficient memory available on the device to verify
* the certificate (if the free memory drops below 8000 bytes during certain * the certificate (if the free memory drops below 8000 bytes during certain
* points in the connection, SSLCLient will fail). * points in the connection, SSLClient will fail).
* @pre There must be a trust anchor given to the ctor that corresponds to * @pre There must be a trust anchor given to the constructor that corresponds to
* the certificate provided by the IP address being connected to. For more * the certificate provided by the IP address being connected to. For more
* information check out the wiki on the pycert-bearssl tool. * information check out TrustAnchors.md .
* @pre The analog pin passed to the ctor must be set to input, and must
* be wired to something sort of random (floating is fine).
* *
* @param host The cstring host ("www.google.com") * @param host The hostname as a null-terminated c-string ("www.google.com")
* @param port the port to connect to (443) * @param port The port to connect to on the host (443 for HTTPS)
* @returns 1 of success, 0 if failure (as found in EthernetClient). * @returns 1 of success, 0 if failure
*/ */
virtual int connect(const char *host, uint16_t port) { return connect_impl(host, port); } virtual int connect(const char *host, uint16_t port) { return connect_impl(host, port); }
@ -208,36 +166,14 @@ public:
/** /**
* @brief Write some bytes to the SSL connection * @brief Write some bytes to the SSL connection
* *
* Assuming all preconditions are met, this function waits for BearSSL * Assuming all preconditions are met, this function writes data to the BearSSL IO
* to be ready for data to be sent, then writes data to the BearSSL IO * buffer, BUT does not initially send the data. Instead, you must call
* buffer, BUT does not initally send the data. Insead, it is * SSLClient::available or SSLClient::flush, which will detect that
* then checked if the BearSSL IO buffer is full, and if so, this function
* waits until BearSSL has flushed the buffer (written it to the
* network client) and fills the buffer again. If the function finds
* that the BearSSL buffer is not full, it returns the number of
* bytes written. In other words, this function will only write data
* to the network if the BearSSL IO buffer is full. Instead, you must call
* SSLClient::availible or SSLClient::flush, which will detect that
* the buffer is ready for writing, and will write the data to the network. * the buffer is ready for writing, and will write the data to the network.
* * Alternatively, if this function is requested to write a larger amount of data than SSLClientImpl::m_iobuf
* This was implemented as a buffered function because users of Arduino Client * can handle, data will be written to the network in pages the size of SSLClientImpl::m_iobuf until
* libraries will often write to the network as such: * all the data in buf is sent--attempting to keep all writes to the network grouped together. For information
* @code{.cpp} * on why this is the case check out README.md .
* Client client;
* ...
* client.println("GET /asciilogo.txt HTTP/1.1");
* client.println("Host: arduino.cc");
* client.println("Connection: close");
* while (!client.available()) { ... }
* ...
* @endcode
* This is fine with most network clients. With SSL, however, if we are encryting and
* writing to the network every write() call this will result in a lot of
* small encryption tasks. Encryption takes a lot of time and code, and in general
* the larger the batch we can do it in the better. For this reason, write()
* implicitly buffers until SSLClient::availible is called, or until the buffer is full.
* If you would like to trigger a network write manually without using the SSLClient::available,
* you can also call SSLClient::flush, which will write all data and return when finished.
* *
* The implementation for this function can be found in SSLClientImpl::write_impl(const uint8_t*, size_t) * The implementation for this function can be found in SSLClientImpl::write_impl(const uint8_t*, size_t)
* *
@ -256,18 +192,18 @@ public:
* @brief Returns the number of bytes availible to read from the SSL Socket * @brief Returns the number of bytes availible to read from the SSL Socket
* *
* This function updates the state of the SSL engine (including writing any data, * This function updates the state of the SSL engine (including writing any data,
* see SSLClient::write) and as a result should be called periodically when writing * see SSLClient::write) and as a result should be called periodically when expecting data.
* or expecting data. Additionally, since this function returns zero if there are * Additionally, since if there are no bytes and if SSLClient::connected is false
* no bytes and if SSLClient::connected is false (this same behavior is found * this function returns zero (this same behavior is found
* in EthernetClient), it is prudent to ensure in your own code that the * in EthernetClient), it is prudent to ensure in your own code that the
* preconditions are met before checking this function to prevent an ambigious * preconditions are met before checking this function to prevent an ambiguous
* result. * result.
* *
* The implementation for this function can be found in SSLClientImpl::available * The implementation for this function can be found in SSLClientImpl::available
* *
* @pre SSLClient::connected must be true. * @pre SSLClient::connected must be true.
* *
* @returns The number of bytes availible (can be zero), or zero if any of the pre * @returns The number of bytes available (can be zero), or zero if any of the pre
* conditions aren't satisfied. * conditions aren't satisfied.
*/ */
virtual int available() { return available_impl(); } virtual int available() { return available_impl(); }
@ -280,38 +216,14 @@ public:
/** /**
* @brief Read size bytes from the SSL socket buffer, copying them into *buf, and return the number of bytes read. * @brief Read size bytes from the SSL socket buffer, copying them into *buf, and return the number of bytes read.
* *
* This function checks if bytes are ready to be read by calling SSLClient::availible, * This function checks if bytes are ready to be read by calling SSLClient::available,
* and if so copies size number of bytes from the IO buffer into the buf pointer, and deletes * and if so copies size number of bytes from the IO buffer into the buf pointer.
* that number of bytes from the SSLClient buffer. Data read using this function will not * Data read using this function will not
* include any SSL or socket commands, as the Client and BearSSL will capture those and * include any SSL or socket commands, as the Client and BearSSL will capture those and
* process them seperatley. * process them separately.
* *
* It should be noted that a common problem I encountered with SSL connections is * If you find that you are having a lot of timeout errors, SSLClient may be experiencing a buffer
* buffer overflow, caused by the server sending too much data at once. This problem * overflow. Checkout README.md for more information.
* is caused by the microcontroller being unable to copy and decrypt data faster
* than it is being recieved, forcing some data to be discarded. This usually puts BearSSL
* in an invalid state in which it is unable to recover, causing SSLClient to close
* the connection with a write error. If you are experiencing frequent timeout problems,
* this could be the reason why.
*
* In order to remedy this problem the device must be able to read the data faster than
* it is being recieved, or have a cache large enough to store the entire recieve payload.
* Since SSL's encryption forces the device to read slowly, this means we must increase
* the cache size. Depending on your platform, there are a number of ways this can be
* done:
* - Sometimes your communication sheild will have an internal buffer, which can be expanded
* through the driver code. This is the case with the Arduino Ethernet library (in the form
* of the MAX_SOCK_NUM and ETHERNET_LARGE_BUFFERS macros), however the library must be
* modified for the change to take effect.
* - SSLClient has an internal buffer SSLClientImpl::m_iobuf, which can be expanded. This will have very
* limited usefulness, however, as BearSSL limits the amount of data that can be processed
* based on the stage in the SSL handshake.
* - If none of the above are viable, it is possible to implement your own Client class which
* has an internal buffer much larger than both the driver and BearSSL. This would require
* in-depth knowlege of programming and the communication shield you are working with.
* Another important question to ask with this problem is: do I need to acsess this website?
* Often times there are other ways to get data that we need that do the same thing,
* and these other ways may offer smaller and more managable response payloads.
* *
* The implementation for this function can be found in SSLClientImpl::read_impl(uint8_t*, size_t) * The implementation for this function can be found in SSLClientImpl::read_impl(uint8_t*, size_t)
* *
@ -328,15 +240,15 @@ public:
* @brief view the first byte of the buffer, without removing it from the SSLClient Buffer * @brief view the first byte of the buffer, without removing it from the SSLClient Buffer
* The implementation for this function can be found in SSLClientImpl::peek * The implementation for this function can be found in SSLClientImpl::peek
* @pre SSLClient::available must be >0 * @pre SSLClient::available must be >0
* @returns The first byte recieved, or -1 if the preconditions are not satisfied (warning: * @returns The first byte received, or -1 if the preconditions are not satisfied (warning:
* do not use if your data may be -1, as the return value is ambigious) * do not use if your data may be -1, as the return value is ambiguous)
*/ */
virtual int peek() { return peek_impl(); } virtual int peek() { return peek_impl(); }
/** /**
* @brief Force writing the buffered bytes from SSLClient::write to the network. * @brief Force writing the buffered bytes from SSLClient::write to the network.
* This function is blocking until all bytes from the buffer are written. For * This function is blocking until all bytes from the buffer are written. For
* an explanation of how writing with SSLClient works, please see SSLCLient::write. * an explanation of how writing with SSLClient works, please see SSLClient::write.
* The implementation for this function can be found in SSLClientImpl::flush. * The implementation for this function can be found in SSLClientImpl::flush.
*/ */
virtual void flush() { return flush_impl(); } virtual void flush() { return flush_impl(); }
@ -353,9 +265,9 @@ public:
/** /**
* @brief Check if the device is connected. * @brief Check if the device is connected.
* Use this function to determine if SSLClient is still connected and a SSL connection is active. * Use this function to determine if SSLClient is still connected and a SSL connection is active.
* It should be noted that SSLClient::availible should be prefered over this function for rapid * It should be noted that SSLClient::available should be preferred over this function for rapid
* polling--both functions send and recieve data to the Client device, however SSLClient::availible * polling--both functions send and receive data with the SSLClient::m_client device, however SSLClient::available
* has some delays built in to protect the Client device from being polled too frequently. * has some delays built in to protect SSLClient::m_client from being polled too frequently.
* *
* The implementation for this function can be found in SSLClientImpl::connected_impl. * The implementation for this function can be found in SSLClientImpl::connected_impl.
* *
@ -368,16 +280,16 @@ public:
//======================================== //========================================
/** /**
* @brief Get a sesssion reference corressponding to a host and IP, or a reference to a emptey session if none exist * @brief Get a session reference corresponding to a host and IP, or a reference to a empty session if none exist
* *
* If no session corresponding to the host and ip exist, then this function will cycle through * If no session corresponding to the host and IP exist, then this function will cycle through
* sessions in a rotating order. This allows the ssession cache to continuially store sessions, * sessions in a rotating order. This allows the session cache to continually store sessions,
* however it will also result in old sessions being cleared and returned. In general, it is a * however it will also result in old sessions being cleared and returned. In general, it is a
* good idea to use a SessionCache size equal to the number of domains you plan on connecting to. * good idea to use a SessionCache size equal to the number of domains you plan on connecting to.
* *
* The implementation for this function can be found at SSLClientImpl::get_session_impl. * The implementation for this function can be found at SSLClientImpl::get_session_impl.
* *
* @param host A hostname c string, or NULL if one is not availible * @param host A hostname c string, or NULL if one is not available
* @param ip An IP address * @param ip An IP address
* @returns A reference to an SSLSession object * @returns A reference to an SSLSession object
*/ */
@ -388,13 +300,13 @@ public:
* *
* The implementation for this function can be found at SSLClientImpl::remove_session_impl. * The implementation for this function can be found at SSLClientImpl::remove_session_impl.
* *
* @param host A hostname c string, or NULL if one is not availible * @param host A hostname c string, or NULL if one is not available
* @param ip An IP address * @param ip An IP address
*/ */
virtual void removeSession(const char* host, const IPAddress& addr) { return remove_session_impl(host, addr); } virtual void removeSession(const char* host, const IPAddress& addr) { return remove_session_impl(host, addr); }
/** /**
* @brief Get the meximum number of SSL sessions that can be stored at once * @brief Get the maximum number of SSL sessions that can be stored at once
* @returns The SessionCache template parameter. * @returns The SessionCache template parameter.
*/ */
virtual size_t getSessionCount() const { return SessionCache; } virtual size_t getSessionCount() const { return SessionCache; }
@ -437,7 +349,7 @@ public:
} }
} }
/** @brief returns a refernence to the client object stored in this class. Take care not to break it. */ /** @brief returns a reference to the client object stored in this class. Take care not to break it. */
C& getClient() { return m_client; } C& getClient() { return m_client; }
protected: protected:

View file

@ -135,6 +135,7 @@ size_t SSLClientImpl::write_impl(const uint8_t *buf, size_t size) {
size_t alen; size_t alen;
unsigned char *br_buf = br_ssl_engine_sendapp_buf(&m_sslctx.eng, &alen); unsigned char *br_buf = br_ssl_engine_sendapp_buf(&m_sslctx.eng, &alen);
size_t cur_idx = 0; size_t cur_idx = 0;
bool did_overflow = false;
// while there are still elements to write // while there are still elements to write
while (cur_idx < size) { while (cur_idx < size) {
// run until the ssl socket is ready to write, unless we've already written // run until the ssl socket is ready to write, unless we've already written
@ -152,8 +153,13 @@ size_t SSLClientImpl::write_impl(const uint8_t *buf, size_t size) {
// so we only send the smallest of the buffer size or our data size - how much we've already sent // so we only send the smallest of the buffer size or our data size - how much we've already sent
const size_t cpamount = m_write_idx + (size - cur_idx) > alen ? alen : size - cur_idx; const size_t cpamount = m_write_idx + (size - cur_idx) > alen ? alen : size - cur_idx;
memcpy(br_buf + m_write_idx, buf + cur_idx, cpamount); memcpy(br_buf + m_write_idx, buf + cur_idx, cpamount);
// if we filled the buffer, reset m_write_idx // if we filled the buffer, reset m_write_idx, and mark the data for sending
if (cpamount == alen) m_write_idx = 0; // or if we've overflowed since we're writing to the network already we may as well finish
if (cpamount == alen || did_overflow) {
m_write_idx = 0;
br_ssl_engine_sendapp_ack(&m_sslctx.eng, alen);
did_overflow = true;
}
// else increment // else increment
else m_write_idx += cpamount; else m_write_idx += cpamount;
// increment the buffer pointer // increment the buffer pointer

View file

@ -30,7 +30,7 @@
* @brief Static constants defining the possible errors encountered. * @brief Static constants defining the possible errors encountered.
* *
* If SSLClient encounters an error, it will generally output * If SSLClient encounters an error, it will generally output
* logs into the serial moniter. If you need a way of programmatically * logs into the serial monitor. If you need a way of programmatically
* checking the errors, you can do so with SSLClient::getWriteError(), * checking the errors, you can do so with SSLClient::getWriteError(),
* which will return one of these values. * which will return one of these values.
*/ */
@ -44,7 +44,7 @@ enum Error {
SSL_CLIENT_WRTIE_ERROR, SSL_CLIENT_WRTIE_ERROR,
/** An internal error occurred with BearSSL, check logs for diagnosis. */ /** An internal error occurred with BearSSL, check logs for diagnosis. */
SSL_BR_WRITE_ERROR, SSL_BR_WRITE_ERROR,
/** An internal error occured with SSLClient, and you probably need to submit an issue on Github. */ /** An internal error occurred with SSLClient, and you probably need to submit an issue on Github. */
SSL_INTERNAL_ERROR, SSL_INTERNAL_ERROR,
/** SSLClient detected that there was not enough memory (>8000 bytes) to continue. */ /** SSLClient detected that there was not enough memory (>8000 bytes) to continue. */
SSL_OUT_OF_MEMORY SSL_OUT_OF_MEMORY
@ -53,47 +53,24 @@ enum Error {
/** /**
* @brief Level of verbosity used in logging for SSLClient. * @brief Level of verbosity used in logging for SSLClient.
* *
* Use these values when initializing SSLCLient to set how many logs you * Use these values when initializing SSLClient to set how many logs you
* would like to see in the Serial moniter. * would like to see in the Serial monitor.
*/ */
enum DebugLevel { enum DebugLevel {
/** No logging output */ /** No logging output */
SSL_NONE = 0, SSL_NONE = 0,
/** Only output errors that result in connection failure */ /** Only output errors that result in connection failure */
SSL_ERROR = 1, SSL_ERROR = 1,
/** Ouput errors and warnings (useful when just starting to develop) */ /** Output errors and warnings (useful when just starting to develop) */
SSL_WARN = 2, SSL_WARN = 2,
/** Output errors, warnings, and internal information (very verbose) */ /** Output errors, warnings, and internal information (very verbose) */
SSL_INFO = 3, SSL_INFO = 3,
}; };
/** /** @brief Implementation code to be inherited by SSLClient */
* On error, any function in this class will terminate the socket.
* TODO: Write what this is */
class SSLClientImpl : public Client { class SSLClientImpl : public Client {
public: public:
/** /** See SSLClient::SSLClient */
* @brief initializes SSL contexts for bearSSL
*
* @pre You will need to generate an array of trust_anchors (root certificates)
* based off of the domains you want to make SSL connections to. Check out the
* Wiki on the pycert-bearssl tool for a simple way to do this.
* @pre The analog_pin should be set to input.
* @pre The session_ray must be an array of the size returned by SSLClient::getSessionCount()
* filled with SSLSession objects.
*
* @post set_client must be called immediatly after to set the client class
* pointer and Session pointer.
*
* @param trust_anchors Trust anchors used in the verification
* of the SSL server certificate, generated using the `brssl` command
* line utility. For more information see the samples or bearssl.org
* @param trust_anchors_num The number of trust anchors stored
* @param analog_pin An analog pin to pull random bytes from, used in seeding the RNG
* @param session_ray A pointer to the array of SSLSessions created by SSLClient
* @param debug whether to enable or disable debug logging, must be constexpr
*/
explicit SSLClientImpl(const br_x509_trust_anchor *trust_anchors, explicit SSLClientImpl(const br_x509_trust_anchor *trust_anchors,
const size_t trust_anchors_num, const int analog_pin, const DebugLevel debug); const size_t trust_anchors_num, const int analog_pin, const DebugLevel debug);
@ -185,7 +162,7 @@ private:
int m_start_ssl(const char* host, SSLSession& ssl_ses); int m_start_ssl(const char* host, SSLSession& ssl_ses);
/** run the bearssl engine until a certain state */ /** run the bearssl engine until a certain state */
int m_run_until(const unsigned target); int m_run_until(const unsigned target);
/** proxy for availble that returns the state */ /** proxy for available that returns the state */
unsigned m_update_engine(); unsigned m_update_engine();
/** utility function to find a session index based off of a host and IP */ /** utility function to find a session index based off of a host and IP */
int m_get_session_index(const char* host, const IPAddress& addr) const; int m_get_session_index(const char* host, const IPAddress& addr) const;

24
src/SSLSession.cpp Normal file
View file

@ -0,0 +1,24 @@
#include "SSLSession.h"
/* See SSLSession.h */
void SSLSession::set_parameters(const IPAddress& ip, const char* hostname) {
// copy the hostname
if (hostname != NULL) m_hostname = hostname;
// or if there's no hostname, clear the string
else m_hostname = "";
// and the IP address
m_ip = ip;
// check if both values are valid, and if so set valid to true
if (m_ip != INADDR_NONE && session_id_len > 0
&& (hostname == NULL || m_hostname)) m_valid_session = true;
// else clear
else clear_parameters();
}
/* see SSLSession.h */
void SSLSession::clear_parameters() {
// clear the hostname , ip, and valid session flags
m_hostname = "";
m_ip = INADDR_NONE;
m_valid_session = false;
}

View file

@ -26,6 +26,8 @@
*/ */
#include "bearssl.h" #include "bearssl.h"
#include "Arduino.h"
#include "IPAddress.h"
#ifndef SSLSession_H_ #ifndef SSLSession_H_
#define SSLSession_H_ #define SSLSession_H_
@ -104,19 +106,7 @@ public:
* Take care that this value is corrent, SSLSession performs no validation * Take care that this value is corrent, SSLSession performs no validation
* of the hostname. * of the hostname.
*/ */
void set_parameters(const IPAddress& ip, const char* hostname = NULL) { void set_parameters(const IPAddress& ip, const char* hostname = NULL);
// copy the hostname
if (hostname != NULL) m_hostname = hostname;
// or if there's no hostname, clear the string
else m_hostname = "";
// and the IP address
m_ip = ip;
// check if both values are valid, and if so set valid to true
if (m_ip != INADDR_NONE && session_id_len > 0
&& (hostname == NULL || m_hostname)) m_valid_session = true;
// else clear
else clear_parameters();
}
/** /**
* @brief delete the parameters and invalidate the session * @brief delete the parameters and invalidate the session
@ -124,12 +114,7 @@ public:
* this function preserves the String object, allowing it * this function preserves the String object, allowing it
* to better handle the dynamic memory needed. * to better handle the dynamic memory needed.
*/ */
void clear_parameters() { void clear_parameters();
// clear the hostname , ip, and valid session flags
m_hostname = "";
m_ip = INADDR_NONE;
m_valid_session = false;
}
/** @brief returns a pointer to the ::br_ssl_session_parameters component of this class */ /** @brief returns a pointer to the ::br_ssl_session_parameters component of this class */
br_ssl_session_parameters* to_br_session() { return (br_ssl_session_parameters *)this; } br_ssl_session_parameters* to_br_session() { return (br_ssl_session_parameters *)this; }
@ -142,4 +127,6 @@ private:
IPAddress m_ip; IPAddress m_ip;
}; };
#endif /* SSLSession_H_ */ #endif /* SSLSession_H_ */

View file

@ -14,7 +14,7 @@
# OpenSSL can't be found then try installing egenix's prebuilt # OpenSSL can't be found then try installing egenix's prebuilt
# PyOpenSSL library and OpenSSL lib: # PyOpenSSL library and OpenSSL lib:
# http://www.egenix.com/products/python/pyOpenSSL/ # http://www.egenix.com/products/python/pyOpenSSL/
# # certifi - Install with 'sudo pip install certifi' (omit sudo on windows)
import cert_util import cert_util
import click import click
import certifi import certifi
@ -68,6 +68,9 @@ def download(port, cert_var, cert_length_var, output, use_store, keep_dupes, dom
pycert download --output data.h google.com adafruit.com pycert download --output data.h google.com adafruit.com
Note that the certificates will be validated before they are downloaded! Note that the certificates will be validated before they are downloaded!
""" """
# if array is emptey, exit
if len(domain) is 0:
return
# prepare the root certificate store # prepare the root certificate store
cert_obj_store = cert_util.parse_root_certificate_store(use_store) cert_obj_store = cert_util.parse_root_certificate_store(use_store)
cert_dict = dict([(cert.get_subject().hash(), cert) for cert in cert_obj_store]) cert_dict = dict([(cert.get_subject().hash(), cert) for cert in cert_obj_store])
@ -111,6 +114,9 @@ def convert(cert_var, cert_length_var, output, use_store, keep_dupes, cert):
Example of converting foo.pem and bar.pem certificates into data.h: Example of converting foo.pem and bar.pem certificates into data.h:
pycert convert foo.pem bar.pem pycert convert foo.pem bar.pem
""" """
# if array is emptey, exit
if len(cert) is 0:
return
# prepare root certificate store # prepare root certificate store
cert_obj_store = cert_util.parse_root_certificate_store(use_store) cert_obj_store = cert_util.parse_root_certificate_store(use_store)
cert_dict = dict([(cert.get_subject().hash(), cert) for cert in cert_obj_store]) cert_dict = dict([(cert.get_subject().hash(), cert) for cert in cert_obj_store])