When learning something new there is little better than a concrete example to ease things along.

I'll use stunnel 4.15 as a starting point. It is what I used to help develop nss_compat_ossl.

The first thing we have to do is tell autoconf about nss_compat_ossl and possibly override any default OpenSSL settings. This includes any include directories, libraries and any definitions (like HAVE_OPENSSL).

Here is a diff of the stunnel configure.ac. I'll go into the details of what and why after the fold.

--- configure.ac.orig   2007-07-20 10:45:57.000000000 -0400
+++ configure.ac        2007-07-20 13:52:44.000000000 -0400
@@ -101,6 +101,9 @@
 
 AC_MSG_NOTICE([**************************************** SSL])
 checkssldir() { :
+    if ! test -z "$nss_compat"; then
+        return 0
+    fi
     if test -f "$1/include/openssl/ssl.h"
     then AC_DEFINE(HAVE_OPENSSL)
         ssldir="$1"
@@ -113,8 +116,30 @@
     return 1
 }
 
+OPT_NSS_COMPAT=no
 # Check for SSL directory
 AC_MSG_CHECKING([for SSL directory])
+AC_ARG_WITH(nss_compat,
+[  --with-nss_compat=DIR   location of installed NSS compatibility SSL libraries/include files], OPT_NSS_COMPAT=$withval)
+
+if test X"$OPT_NSS_COMPAT" != Xno; then
+    if test "x$OPT_NSS_COMPAT" = "xyes"; then
+     check=`pkg-config --version 2>/dev/null`
+     if test -n "$check"; then
+       addlib=`pkg-config --libs nss`
+       addcflags=`pkg-config --cflags nss`
+     fi
+    else
+      # Without pkg-config, we'll kludge in some defaults
+      addlib="-L$OPT_NSS_COMPAT/lib -lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 -lpthread -ldl"
+      addcflags="-I$OPT_NSS_COMPAT/include"
+    fi
+    CFLAGS="$CFLAGS $addcflags"
+    LIBS="$LIBS $addlib -lnss_compat_ossl"
+    nss_compat="yes"
+    AC_DEFINE(HAVE_NSS_COMPAT)
+    echo "Using nss_compat_ossl instead of OpenSSL"
+fi
 AC_ARG_WITH(ssl,
 [  --with-ssl=DIR          location of installed SSL libraries/include files],
     [
@@ -130,7 +155,7 @@
         done
     ]
 )
-if test -z "$ssldir"
+if test -z "$ssldir" -a -z "$nss_compat"
 then AC_MSG_RESULT([Not found])
     echo
     echo "Couldn't find your SSL library installation dir"


The stunnel configure.ac does things a little differently than others I've seen. They declare a separate function to try to find OpenSSL then set things up from there. What we need to do is short circuit this. There is no AC_UNDEFINE so our stuff has to come first.

The first change in the file is in checkssldir(). This bails out if nss_compat_ossl has been selected. We check for a global variable. The interesting stuff comes next.

The meat is in the --with-nss_compat field. If it it gets set to a directory then we use that path (like --with-nss_compat=/usr/local) and we set CFLAGS and LIBS as best we can. If it is set without a DIR (e.g --with-nss_compat) then we use pkg-config to get things setup for us which is more likely to produce the results we want.

The important things to set are:

AC_DEFINE(HAVE_NSS_COMPAT)
LIBS to include the NSS and NSPR libraries and potentially the path they are installed in (if not in /usr/lib)
CFLAGS or INCLUDES to include the NSS and NSPR include file locations

The change of "if test -z "$ssldir -a -z "$nss_compat" we want to crap out if neither OpenSSL nor nss_compat_ossl are set.

Now that we can configure things properly you would run something like:

% autoconf
% ./configure --with-nss_compat


You should probably verify that the Makefiles define -DHAVE_NSS_COMPAT=1 and that LIBS and CFLAGS are sane.

Next we move onto the code changes. You can tackle it either as an iterative or analytical process. I tend to use the iterative approach myself. This involves trying to build and tackling errors as they come up.

In the stunnel case we run into a problem quite early. common.h is trying to include some OpenSSL headers which it can't find. We need it to use our header instead:

--- common.h.orig       2007-07-20 12:21:27.000000000 -0400
+++ common.h    2007-07-20 12:23:23.000000000 -0400
@@ -287,6 +287,7 @@
 
 /**************************************** OpenSSL headers */
 
+#ifndef HAVE_NSS_COMPAT
 #ifdef HAVE_OPENSSL
 #include <openssl/lhash.h>
 #include <openssl/ssl.h>
@@ -302,6 +303,9 @@
 #include <err.h>
 #include <crypto.h> /* for CRYPTO_* and SSLeay_version */
 #endif
+#else
+#include <nss_compat_ossl/nss_compat_ossl.h>
+#endif
 
 /**************************************** Other defines */


When using an iterative approach errors tend to appear as undeclared defines and functions such as this in ssl.c:

ssl.c: In function ‘init_compression’:
ssl.c:63: error: ‘COMP_METHOD’ undeclared (first use in this function)
ssl.c:63: error: (Each undeclared identifier is reported only once
ssl.c:63: error: for each function it appears in.)
ssl.c:63: error: ‘cm’ undeclared (first use in this function)
ssl.c:69: warning: implicit declaration of function ‘COMP_zlib’
ssl.c:74: warning: implicit declaration of function ‘COMP_rle’
ssl.c:81: error: ‘NID_undef’ undeclared (first use in this function)
ssl.c:85: warning: implicit declaration of function ‘SSL_COMP_add_compression_method’
gmake: *** [ssl.o] Error 1


nss_compat_ossl doesn't support SSL compression so we need to skip this function. One way to do it is with:

--- ssl.c.orig  2007-07-20 14:09:42.000000000 -0400
+++ ssl.c       2007-07-20 14:11:06.000000000 -0400
@@ -59,6 +59,7 @@
 }
 
 static void init_compression(void) {
+#ifdef HAVE_OPENSSL
     int id=0;
     COMP_METHOD *cm=NULL;
     char *name="unknown";
@@ -87,6 +88,7 @@
         exit(1);
     }
     s_log(LOG_INFO, "Compression enabled using %s method", name);
+#endif
 }
 
 static int init_prng(void) {


You could easily use the reverse of this and use #ifndef HAVE_NSS_COMPAT. The choice is yours.

Our next obstacle is in options.c. A slew of missing BIO_ errors are reported, way to many to include here. A further investigation reveals that the function generates a base64 value from a string passed in. In this case NSS provides a single function to do this so we can patch this with:

--- options.c.orig      2007-07-20 13:40:24.000000000 -0400
+++ options.c   2007-07-20 13:48:19.000000000 -0400
@@ -1263,6 +1263,9 @@
 }
 
 static char *base64(char *str) { /* Allocate base64-encoded string */
+#ifdef HAVE_NSS_COMPAT
+     return BTOA_DataToAscii(str, strlen(str));
+#else
     BIO *bio, *b64;
     char *retval;
     int len;
@@ -1284,6 +1287,7 @@
     BIO_read(bio, retval, len);
     BIO_free(bio);
     return retval;
+#endif
 }


The last set of problems is a bit more challenging. Lets break down each change in the diff.

The first change is in verify_init():

--- ctx.c.orig  2007-07-20 13:51:00.000000000 -0400
+++ ctx.c       2007-07-20 13:52:10.000000000 -0400
@@ -304,6 +304,7 @@
     }
 
     if(section->crl_file || section->crl_dir) { /* setup CRL store */
+#ifdef HAVE_OPENSSL
         revocation_store=X509_STORE_new();
         if(!revocation_store) {
             sslerror("X509_STORE_new");
@@ -341,6 +342,7 @@
             }
             s_log(LOG_DEBUG, "CRL directory set to %s", section->crl_dir);
         }
+#endif
     }
 
     SSL_CTX_set_verify(ctx, section->verify_level==SSL_VERIFY_NONE ?

NSS handles CRLs via its database. OpenSSL imports them from flat files on startup. So we can #ifdef around a fairly large block of code as NSS will handle all this for us. It does mean though that the user that runs the program will need to preload the CRL into the NSS database using /usr/bin/crlutil before starting stunnel. So this should be documented somewhere.

@@ -352,6 +354,7 @@
 
 static int verify_callback(int preverify_ok, X509_STORE_CTX *callback_ctx) {
         /* our verify callback function */
+#ifdef HAVE_OPENSSL
     char txt[STRLEN];
     X509_OBJECT ret;
     SSL *ssl;
@@ -389,11 +392,13 @@
     /* errnum=X509_STORE_CTX_get_error(ctx); */
 
     s_log(LOG_NOTICE, "VERIFY OK: depth=%d, %s", callback_ctx->error_depth, txt);
+#endif
     return 1; /* Accept connection */
 }


This change is also very significant. In both OpenSSL and NSS you can write your own callback to verify certificates, CRLs, etc. nss_compat_ossl does a lot of this for the user so we can #ifdef around this code.  It is possible to go ahead and define a verify_callback that does real work in both but the nss_compat_ossl API is missing a lot of functions typically found in this verification so you will have to proceed cautiously.

nss_compat_ossl automatically checks for certificate trust and valid dates. If there are other things you require you can continue to do them in a verify_callback() but it may be tricky.
 
NSS handles CRL checking automatically so we can ignore all of the following code:

 /* Based on BSD-style licensed code of mod_ssl */
 static int crl_callback(X509_STORE_CTX *callback_ctx) {
+#ifdef HAVE_OPENSSL
     X509_STORE_CTX store_ctx;
     X509_OBJECT obj;
     X509_NAME *subject;
@@ -506,6 +511,7 @@
         }
         X509_OBJECT_free_contents(&obj);
     }
+#endif
     return 1; /* Accept connection */
 }


And finally a very important change. nss_compat_ossl does not necessarily mimic the data structures of OpenSSL so any attempts to directly access parts of a structure will probably fail. In nss_compat_ossl there is only a logical difference between an SSL structure and a SSL context structure. So we can't pass in s->ctx. But we can pass in s to get what we want. This doesn't actually do much since we aren't calculating this data. I included this to demonstrate one possible workaround.

@@ -525,7 +531,11 @@
             SSL_alert_type_string_long(ret),
             SSL_alert_desc_string_long(ret));
     else if(where==SSL_CB_HANDSHAKE_DONE)
+#ifdef HAVE_NSS_COMPAT
+        print_stats(s);
+#else
         print_stats(s->ctx);
+#endif
 }
 
 static void print_stats(SSL_CTX *ctx) { /* print statistics */


Now stunnel should build but it isn't quite ready to run yet. With NSS you have to tell it where it can find the key and certificate databases. We use the environment variable SSL_DIR to specify one (the default is /etc/pki/nssdb).

Congratulations. You've ported your first application with just a handful of changes to the code.