How to handle OpenSSL and not get hurt and what does that library call really do?

From GridWiki
Jump to: navigation, search

Back to top How to handle OpenSSL and not get hurt

Contents

SSL_CTX_set_verify()

This function will setup the SSL_CTX to verify client certificates in a particular way and offers the possibility to register your own callback function. The callback function will be called after the internal OpenSSL function have tried to verify a certificate in the certificate chain. In particular the certificates from the chain originated from the SSL handshake or the user input are evaluated here, not the trust anchors (aka CA file or CA path). This doesn't exclude CA certificates to be entered into the routines from the SSL or user input. This must be expected. The root CA can be supplied too, but the build in trust anchors win the evaluation.


The client side verification options can be manipulated with the second argument and can be set to:

  • SSL_VERIFY_NONE: Don't do any verification on the client certificate. Do not use together with SSL_VERIFY_PEER.
  • SSL_VERIFY_PEER: Expect a client certificate. The peer may not supply any certificate. Do not use together with SSL_VERIFY_NONE.
  • SSL_VERIFY_FAIL_IF_NO_PEER_CERT: Receiving a certificate is mandatory. Fail the SSL_connect() or SSL_accept() when the peer can't supply a certificate (chain) with a "handshake failure". This flag must be used together with SSL_VERIFY_PEER.
  • SSL_VERIFY_CLIENT_ONCE : Only request a client certificate on the initial TLS/SSL handshake. Do not ask for a client certificate again in case of a renegotiation. This flag must be used together with SSL_VERIFY_PEER. I kind of regard it to be unsafe, because it bypasses CRL check opportunities. This could be a problem to some services.

All the options can be logically OR'ed, with the exception of the SSL_VERIFY_NONE and SSL_VERIFY_PEER. They are mutually exclusive.

X509_STORE_CTX_get_chain() vs. X509_STORE_CTX_get1_chain()

There exists X509_STORE_CTX_get_chain(X509_STORE_CTX *ctx) and X509_STORE_CTX_get1_chain(X509_STORE_CTX *ctx). The difference is that the X509_STORE_CTX_get_chain() will only return a reference to the certificate chain (type STACK_OF (X509) *) from within the X509_STORE_CTX structure and the X509_STORE_CTX_get1_chain() will make a duplicate certificate chain that will need to be free()'d. Internally the X509_STORE_CTX_get1_chain() will duplicate the STACK_OF(X509) object and increase the reference counter of all the certificates in the existing stack of X509.

Proper memory liberation of a STACK_OF (X509) *

Freeing a STACK_OF (X509) * is not to be done with just free(). That will create a memory leak. Also a X509_free (X509) * and using a STACK_OF (X509) * as input will create a memory leak. The proper way to free a STACK_OF (X509) * is to use sk_X509_pop_free(st, free_func), where for free_func you should use X509_free. Example: sk_X509_pop_free(chain, X509_free). Warning: do not get confused with sk_X509_free() which will accept the STACK_OF (<type>) *, but does not pop the stack to free all the individual certificates of the chain (which will be equal to an instant memory leakage for any certificate chain longer then one certificate).

Getting the oneline notation of a Subject DN and Issuer DN (properly)

To get the issuer DN and subject DN you can use constructions like:

char * cert_DN   = X509_NAME_oneline (X509_get_subject_name (cert), NULL, 0);
char * issuer_DN = X509_NAME_oneline (X509_get_issuer_name (cert), NULL, 0);

or

char cert_DN[255];
char issuer_DN[255];
X509_NAME_oneline (X509_get_subject_name (cert), cert_DN, 255);
X509_NAME_oneline (X509_get_issuer_name (cert), issuer_DN, 255);

In the first construction you'll need to free both the cert_DN and the issuer_DN. In the second example a static buffer is used and filled by the X509_NAME_oneline() routines.

Verification depth extensions and supporting unlimited proxy certificate delegations

OpenSSL uses a default depth of 9 (don't ask why, it just is).

To cope with Subordinate CAs we have to extend the verification depth to be able to hold the certificate chain (could contain a lot of delegations) and all the CA certificate, which might not be added to the certificate chain itself but would still be lingering in the X509 CA directory lookup functions.

After setting up a X509_STORE object, by adding the lookup functions for the CA directory, adding the verification callback function (for proper proxy support), and setting the proper flags... you can create a X509_STORE_CTX object from it with X509_STORE_CTX_new(). Then you can initialize the X509_STORE_CTX object with the certificate chain and the call to X509_STORE_CTX_init(). After setting the purpose to the proper setting and the extra flag if the OpenSSL version is higher then 0.9.8, we can finally get to the chase and upgrade the verification depth to something more usefull.

Our solution (for now) is to upgrade the depth to be the number of certificates in the chain, plus the OpenSSL default verification depth.

Use X509_STORE_CTX_set_depth() to fit the certificate chain, sub-CAs and root CA into it. We've chosen to check for the certificate chain's depth, plus the OpenSSL of 9. The X509_STORE_CTX_set_depth() is a macro in OpenSSL 0.9.7a and it's implemented as a wrapper function in OpenSSL 0.9.8a.

/* Alter verification depth to fit the certificate chain, sub-CAs and root CA */
X509_STORE_CTX_set_depth (verify_ctx, depth + VERIFICATION_CHAIN_DEPTH_EXTENSION);

Path length constraints

Path length constraints for CA certificates (RFC 5280)

The OpenSSL X509 structure has a field called ex_pathlen which holds the parsed pathlen value of the certificate. This is only of use for CA certificates (see next section for Proxy certificates). It means that beyond this point the depth of subordinate CAs in the chain can not exceed the Path Length value. See RFC 3280 and the newer RFC 5280 for details.

Location in the X509 struct:

X509 * certificate;
certificate->ex_pathlen

On violation OpenSSL will trigger the error code: X509_V_ERR_PATH_LENGTH_EXCEEDED

Path length constraints for Proxy certificates (RFC 3820)

To start of, if you're using non-standard GT2/classic/old-style proxies then there is no use of the path length constraint as the concept only focusses on CAs and proxies. And it doesn't make much sense to restrict it in your End-Entity Certificate (EEC).

The OpenSSL X509 structure has a field called ex_pcpathlen which holds the parsed pathlen value of the Proxy certificate from its RFC 3820 matching policy. This is only of use for RFC 3820 Proxy certificates.

It means that beyond this Proxy certificate you are restricted to the number of Proxy certificate delegations by this attribute in your current Proxy certificate.

Location in the X509 struct:

X509 * certificate;
if (certificate->ex_flags & EXFLAG_PROXY)
    certificate->ex_pcpathlen

On violation OpenSSL will trigger the error code: X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED


X509_V_ERR_PATH_LENGTH_EXCEEDED

This error gets triggered when a CA certificate indicates to have a path length constraint set and the certificate chain to evaluate violates the set limitation. When set in a CA certificate this means that you can only have pathlen sub-CA.

Example OK:

Root CA (no path len constaint)
    \_ Sub CA (pathlen = 1)
        \_ Sub Sub CA (no path len constraint)
            \_ EEC (client or host certificate)

Example in error:

Root CA (no path len constaint)
    \_ Sub CA (pathlen = 0)
        \_ Sub Sub CA (no path len constraint)
            \_ EEC (client or host certificate)


X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED

This error code only applies for RFC 3820 Proxy certificates. Old-style (Globus Toolkit 2) proxies do not feature these constructs in the certificate. This error gets triggered when a Proxy certificate indicates to have a path length constraint set and the certificate chain to evaluate violates the set limitation. When set in a Proxy certificate this means that you can only have pathlen Proxy delegations in the chain following the current Proxy.

Example OK:

Root CA (no path len constaint)
    \_ Sub CA (no path len constaint)
        \_ Sub Sub CA (no path len constraint)
            \_ EEC (client or host certificate)
                \_ Proxy (no path len constraint)
                    \_ Proxy (pathlen = 1)
                        \_ Proxy (no path len constraint)

Example in error:

Root CA (no path len constaint)
    \_ Sub CA (no path len constaint)
        \_ Sub Sub CA (no path len constraint)
            \_ EEC (client or host certificate)
                \_ Proxy (no path len constraint)
                    \_ Proxy (pathlen = 0)
                        \_ Proxy (no path len constraint)

X509_get_serialNumber(X509 *)

This call grabs the serialnumber from the presented X509 struct in the form of an ASN1_INTEGER.

ASN1 Transformations

ASN1_INTEGER to long

Use ASN1_INTEGER_get()

ASN1_INTEGER to string

Use i2c_ASN1_INTEGER() in two stages:

  1. Determine the length for your buffer: i2c_ASN1_INTEGER(my_asn1int, NULL);
  2. Write into your buffer: i2c_ASN1_INTEGER(my_asn1int, buffer);

From the source code

This converts an ASN1 INTEGER into its content encoding. The internal representation is an ASN1_STRING whose data is a big endian representation of the value, ignoring the sign. The sign is determined by the type: V_ASN1_INTEGER for positive and V_ASN1_NEG_INTEGER for negative.

Positive integers are no problem: they are almost the same as the DER encoding, except if the first byte is >= 0x80 we need to add a zero pad.

Negative integers are a bit trickier... The DER representation of negative integers is in 2s complement form. The internal form is converted by complementing each octet and finally adding one to the result. This can be done less messily with a little trick. If the internal form has trailing zeroes then they will become FF by the complement and 0 by the add one (due to carry) so just copy as many trailing zeros to the destination as there are in the source. The carry will add one to the last none zero octet: so complement this octet and add one and finally complement any left over until you get to the start of the string.

Padding is a little trickier too. If the first bytes is > 0x80 then we pad with 0xff. However if the first byte is 0x80 and one of the following bytes

  • is non-zero we pad with 0xff. The reason for this distinction is that 0x80
  • followed by optional zeros isn't padded.

OCSP

OpenSSL handles things in the following order:

  1. SSL_connect()
  2. The verify_callback, which is called after the internal OpenSSL certificate chain verification. You can make custom overrides per error.
  3. The OCSP callback in the case of OCSP stapling.
  4. Start of post SSL checks.

Enabling OCSP stapling client side support

During the construction of an SSL_CTX * you can MUST call SSL_CTX_set_tlsext_status_cb() and optionally SSL_CTX_set_tlsext_status_arg() to register a callback which is triggered by the TLS Extension for certificate Status information to handle the OCSP Response. You can provide an extra argument to the callback by using SSL_CTX_set_tlsext_status_arg().

Example:

SSL_CTX *ctx;
...
SSL_CTX_set_tlsext_status_cb(ctx, ocsp_resp_cb);
SSL_CTX_set_tlsext_status_arg(ctx, arg);

Example OCSP callback

From within the callback you can pull the OCSP_RESPONSE from the SSL object by calling SSL_get_tlsext_status_ocsp_resp(). An example OCSP callback looks like this:

#include <openssl/ocsp.h>
...
static int
ocsp_resp_cb(SSL *s, void *arg) {
   struct sslconn *conn = (struct sslconn *)arg;
   OCSP_RESPONSE *rsp;
   const unsigned char *p;
   int len;
   len = SSL_get_tlsext_status_ocsp_resp(s, &p);
   fprintf(stderr, "OCSP response: ");
   if (!p) {
       fprintf(stderr, "no response sent\n");
       return 1;
   }
   rsp = d2i_OCSP_RESPONSE(NULL, &p, len);
   if (!rsp) {
       fprintf(stderr, "response parse error\n");
       return 1;
   }
   fprintf(stderr, "got stapled response\n");
   /* Record stapled response */
   if (conn) {
       conn->ocsp_stapling = rsp;
   }
   return 1;
}

Generic function

Other functions that are ill explained in the OpenSSL documentation.

PEM_write_X509()

Returns (int)1 on success, or (int)0 on failure.

Views
Personal tools