Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.

2017-01-31

Swift and OpenSSL Part 2: Facing the libraries

OpenSSL is written in plain old C. Which means that we can use it from Swift... right?

Yes, but...

Macro's

While we can reference the header files and link the libraries, openSSL uses macro's in their headers to make some functions more palatable. Unfortunately Swift does not understand all macro's.

If you are not familiar with macro's, macro's are definitions that "replace text with text". But have some extra functionality like parameter passing built in.

This is an example of a macro:

#define SYSerr(f,r)  ERR_PUT_error(ERR_LIB_SYS,(f),(r),OPENSSL_FILE,OPENSSL_LINE)

In C-code when we use the function: SYSerr(f,r)

in reality the function that is called is: ERR_PUT_error(ERR_LIB_SYS,(f),(r),OPENSSL_FILE,OPENSSL_LINE)

During compilation the C pre-processor will replace the first function definition with the second. Before even the real compiler is started.

While Swift does have a pre-processor for C-headers, it is used to translate C-calls to their Swift equivalents. The pre-processor does not have the same capability of the C-preprocessor.

To get back to the example, in Swift the function SYSerr(f,r) does not exist because the pre-processor does not translate it.

But the ERR_PUT_error(ERR_LIB_SYS,(f),(r),OPENSSL_FILE,OPENSSL_LINE) does exist. The pre-processor can handle it just fine.

This means that if we find some code that tells us to use a function (like SYSerr) we can solve this by finding where the macro is defined and then to use the function the macro refers to. So don't despair when auto-complete does not know of a function you just found on the internet, simply do a search on the header files and see how the macro is defined.

OPENSSL_free()

OPENSSL_free(ptr) is one of those macro's. As an example you could try to find out what the proper replacement is. Its a bit tricky as a compile option must be taken into account.

I will give the answer here, but you should try it yourself.

The answer is to replace OPENSSL_free(ptr) with CRYPTO_free(ptr, "", 0)

Pointers

Per documentation, String's are automatically bridged to UnsafePointer<Int8> and UnsafePointer<UInt8>  That is very convenient as in many places we can simply pass a string where the API expects a "char *" or "const char*"

It is good to know that (even though the documentation does not say so) the pointer that is created by the bridge is in fact pointing to a C-string representation of the String, i.e. it is null-terminated.

Some openSSL APIs need a string in an UnsafeMutableRawPointer. I find it convenient to wrap such APIs in a wrapper that receive the string as an input parameter:

func someSslFunctionWrapper(_ str: UnsafePointer<Int8>) {
    let ptr = UnsafeMutableRawPointer(mutating: str)
    // work with ptr, but only inside this function!
}

someSslFunctionWrapper("string-input")

While 'ptr' does point to a null-terminated string, keep in mind that it does point to a copy of the string that was passed into the function. If the API tries to change the 'str' then this approach would not work. I have not encountered such situations yet.

Callbacks

Some openSSL operations require a callback operation. Unfortunately some of the callbacks are passed as

void (*fp) (void)

and are cast in a define statement to the proper signature just before they are passed to in the SSL library. I found this to be a problem as I was not able to find/write Swift trickery to create these.

The way I solved this is by introducing a c-wrapper that takes a Swift function of the proper signature and bridges this to the required signature.

Example:

SSL_CTX_set_tlsext_servername_callback is not available directly, looking for this in the include files gives us:

#define SSL_CTX_set_tlsext_servername_callback(ctx, cb) \
SSL_CTX_callback_ctrl(ctx,SSL_CTRL_SET_TLSEXT_SERVERNAME_CB,(void (*)(void))cb)

Its a macro, thus we need to use SSL_CTX_callback_ctrl

However this function takes a generic function pointer with the signature "() -> Void" (as can also be seen in Xcode auto-complete). Obviously (from the example C-code) that is not what is needed. We ned the proper signature of the callback before we can write the callback.

Tracing the implementation of SSL_CTX_callback_ctrl gives us:

    case SSL_CTRL_SET_TLSEXT_SERVERNAME_CB:
        ctx->tlsext_servername_callback = (int (*)(SSL *, int *, void *))fp;
        break;

Hence the signature of the callback is really:

int (*)(SSL *, int *, void *)

But we cannot use the function with this signature to call SSL_CTX_callback_ctrl. That seems to leave us in a bind. The solution is to create our own C-wrapper function.

In this case our wrapper takes this signature as an input:

void sslCtxSetTlsExtServernameCallback(SSL_CTX *ctx, int (*cb)(SSL *, int *, void *)) {
    SSL_CTX_set_tlsext_servername_callback(ctx, cb);
}

Note that since the wrapper is C code it does makes sense to use the macro again.

It can take some searching in the openSSL sources before the appropriate signature is found. But it is not all that difficult.

Documentation warnings

Depending on how "deep" we need to go with the openSSL libraries the compiler can start to generate 'Documentation warnings'. These are due to "errors" in the comments in the openSSL headers. I found such cases in 'bn.h' and 'ec.h'. If you want to get rid of then, simply accept the xcode suggestions or remove the offending lines entirely. Its only documentation after all...

OpenSSL Documentation.

Sadly the openSSL documentation at the website is not what an API user would like it to be. But do not give up too easily. Sometimes the documentation is there, just not properly linked. When you run into a link error, try again. Sometimes a link from a different page will work.

I have found that a link produced by a search did not work, but a link from an earlier version did. This is a bit difficult to describe; but as you search on the openSSL site you will soon discover that you want to click those links that contain the version number of openSSL that we use. e.g. links that contain "1.1.0". However some of those links are wrong. In that case it can help to click a search result with version "1.0.2" and when that page is found, then click the "1.1.0" version link on that page. Often that will do the trick.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

No comments:

Post a Comment