Using vendored OpenSSL in another framework

(My gut feeling to the following problem is something to do with header file locations. It's not strictly Swift language, but setting up / understanding C library targets in SwiftPM seems to be on-topic. I feel like I'm missing something obvious here, but alas I'm stuck.)

I have a Vapor 3 app that I test/run on the Mac through Xcode 11 and the SwiftPM integration. The Vapor app is deployed on Docker Ubuntu 18.04.

I'm currently trying to use things like PKCS12 operations from OpenSSL in my code. As it is a Vapor app, I thought I could co-opt their vendored SSL framework instead of having to repeat the work, or import something else. So, I import CCryptoOpenSSL at the top of my file and use all the necessary OpenSSL types and functions in the file.

On the Mac, this compiles and runs fine. But when I run this on Linux, I am hit with a failed build with compilation errors that seem to suggest it isn't actually finding the headers, eg:

no type named 'PKCS12' in module 'CCryptoOpenSSL'
no type named 'EVP_PKEY' in module 'CCryptoOpenSSL'
no type named 'X509' in module 'CCryptoOpenSSL'

I'm not sure why this project builds fine in Xcode but doesn't work in Linux and command line swift build. (I can command-click on the symbols in Xcode and it shows me the containing header file, but I'm honestly not sure where that header file is actually 'stored'.)

My Docker configuration is using the Swift slim images, plus openssl to be able to build Vapor and NIO etc in the first place. I sanity-checked the Docker container and openssl headers are present at /usr/include/openssl.

Any pointers here / can you explain the difference between the macOS and Linux environments?


The problem you're describing sounds like you're missing the pkg-config package. pkg-config is used to locate the headers if they're not in standard locations and also makes SwiftPM (and many other build systems) pass the correct linker flags etc. Possibly you're also missing the libssl-dev package.

A few other remarks:

  • do you actually have any of the libssl-dev packages installed (what's the output of dpkg -l | grep libssl?)
  • it seems like you're trying to use the system's OpenSSL and not a vendored version (vendoring typically means that a package vends a library in source or binary form meaning that it brings a dependent library with it and it's not trying to use the system's version). Vendoring OpenSSL is actually hard/impossible (you'd need to namespace all symbols) so it's very good you're trying to use the system's version, otherwise you'd have a number of really gnarly problems
  • Vapor's Crypto doesn't actually export CCryptoOpenSSL as a product so you shouldn't directly depend on it. The only reason this actually works is because SwiftPM doesn't enforce that you're only directly depending on actually exported products. However, this shouldn't be the problem you're facing now.
  • Could you attach the full output of swift build? That would help debugging it further.

First and foremost, thank you for the assistance.

  1. Yes, I am installing the libssl-dev package (Dockerfile for reference)

  2. Whoops, yeah vendoring was prolly the wrong term.

  3. Rather than import CCryptoOpenSSL then, what should I be importing? Should I make my own C library target in my Package.swift instead?

  4. Attached dpkg output and swift build command line output below. I think the Docker build is pretty standard and 'boring', apart from a diversion to install libsass.

dpkg output
ii  libssl-dev:amd64            1.1.1-1ubuntu2.1~18.04.5          amd64        Secure Sockets Layer toolkit - development files
ii  libssl1.0.0:amd64           1.0.2n-1ubuntu5.3                 amd64        Secure Sockets Layer toolkit - shared libraries
ii  libssl1.1:amd64             1.1.1-1ubuntu2.1~18.04.5          amd64        Secure Sockets Layer toolkit - shared libraries
swift build output (via Docker)
Step 1/30 : FROM swift:5.1.2 as builder
 ---> bfc1fa7b54be
Step 2/30 : RUN apt-get -qq update && apt-get -q -y install tzdata
 ---> Using cache
 ---> ac97191bcbfa
Step 3/30 : RUN apt-get -y install software-properties-common
 ---> Using cache
 ---> 19ced235e3d7
Step 4/30 : RUN git clone
 ---> Using cache
 ---> ceffb6045341
Step 5/30 : RUN apt-get -q -y install automake libtool
 ---> Using cache
 ---> 19a206b47dc5
Step 6/30 : WORKDIR libsass
 ---> Using cache
 ---> 8ce8a739482e
Step 7/30 : RUN autoreconf --force --install
 ---> Using cache
 ---> aaa14af10264
Step 8/30 : WORKDIR ..
 ---> Using cache
 ---> a5a0c0b6023c
Step 9/30 : WORKDIR libsass
 ---> Using cache
 ---> 9f385ee921d5
Step 10/30 : RUN ./configure --disable-tests --disable-shared --prefix=/usr
 ---> Using cache
 ---> bd621eed053e
Step 11/30 : WORKDIR ..
 ---> Using cache
 ---> 0ee637178d36
Step 12/30 : RUN make -C libsass -j5
 ---> Using cache
 ---> 2c3b4bc0fe9c
Step 13/30 : RUN make -C libsass -j5 install
 ---> Using cache
 ---> d7841079d4d1
Step 14/30 : RUN apt-get -y install openssl libssl-dev zlib1g-dev
 ---> Using cache
 ---> 083f69f31961
Step 15/30 : WORKDIR /app
 ---> Using cache
 ---> 8d4426ce8eae
Step 16/30 : COPY Sources Sources/
 ---> 4663d53720bc
Step 17/30 : COPY Tests Tests/
 ---> bc92daf51f33
Step 18/30 : COPY Package.swift .
 ---> 15a5f9dacf11
Step 19/30 : RUN mkdir -p /build/bin
 ---> Running in c12e0a1c7c07
Removing intermediate container c12e0a1c7c07
 ---> e87f137c9bfb
Step 20/30 : RUN swift package clean
 ---> Running in 0b6adc654e90
Removing intermediate container 0b6adc654e90
 ---> ba41d3afa13d
Step 21/30 : RUN swift build -c release
 ---> Running in d476f585f346
Completed resolution in 53.57s
Resolving at 1.0.0
Resolving at 1.4.1
Resolving at 1.1.2
Resolving at development
Resolving at 1.0.5
Resolving at 1.4.0
Resolving at 1.4.0
Resolving at 3.1.1
Resolving at 3.2.1
Resolving at 3.3.3
Resolving at 4.5.2
Resolving at 1.14.1
Resolving at 1.0.1
Resolving at 1.0.6
Resolving at 3.9.3
Resolving at 1.3.3
Resolving at 2.1.1
Resolving at 1.0.0
Resolving at 1.0.0
Resolving at 3.4.0
Resolving at 1.5.0
Resolving at 2.3.2
Resolving at 3.1.0
Resolving at 3.1.1
Resolving at 3.2.1
Resolving at 3.3.1
Resolving at 1.0.2
[1/49] Compiling Sass SassRenderer.swift
[2/50] Compiling COperatingSystem libc.swift
[3/51] Compiling NIOPriorityQueue Heap.swift
[4/51] Compiling Debugging Debuggable.swift
[5/51] Compiling cmark xml.c
[6/51] Compiling cmark utf8.c
[7/51] Compiling cmark tagfilter.c
[8/51] Compiling cmark table.c
[9/51] Compiling cmark strikethrough.c
[10/51] Compiling cmark syntax_extension.c
[11/51] Compiling BitByteData BitReader.swift
[12/52] Compiling cmark render.c
[13/52] Compiling cmark scanners.c
[14/52] Compiling cmark registry.c
[15/52] Compiling cmark plugin.c
[16/52] Compiling cmark references.c
[17/52] Compiling cmark plaintext.c
[18/52] Compiling cmark man.c
[19/52] Compiling cmark linked_list.c
[20/52] Compiling cmark node.c
[21/52] Compiling cmark latex.c
[22/52] Compiling ZIPFoundation Archive+MemoryFile.swift
[23/52] Compiling cmark iterator.c
[24/52] Compiling cmark houdini_html_u.c
[25/52] Compiling cmark html.c
[26/52] Compiling cmark houdini_html_e.c
[27/52] Compiling cmark houdini_href_e.c
[28/52] Compiling cmark core-extensions.c
[29/52] Compiling cmark ext_scanners.c
[30/52] Compiling cmark cmark_ctype.c
[31/52] Compiling cmark commonmark.c
[32/52] Compiling cmark cmark.c
[33/52] Compiling cmark buffer.c
[34/52] Compiling cmark inlines.c
[35/52] Compiling cmark arena.c
[36/52] Compiling CNIOZlib empty.c
[37/52] Compiling cmark autolink.c
[38/52] Compiling CNIOSHA1 c_nio_sha1.c
[39/52] Compiling cmark blocks.c
[40/52] Compiling CNIOOpenSSL helpers.c
[41/52] Compiling CNIOOpenSSL shims.c
[42/52] Compiling CNIOLinux ifaddrs-android.c
[43/52] Compiling CNIODarwin shim.c
[44/52] Compiling CNIOLinux shim.c
[45/52] Compiling CCryptoOpenSSL shim.c
[46/52] Compiling CBcrypt blf.c
[47/52] Compiling CBcrypt bcrypt.c
[48/52] Compiling CBase32 base32.c
[49/52] Compiling CNIOHTTPParser c_nio_http_parser.c
[50/52] Compiling c-atomics.c
[51/53] Compiling NIOConcurrencyHelpers atomics.swift
[52/54] Compiling SWCompression 7zCoder+Equatable.swift
/app/.build/checkouts/SWCompression/Sources/BZip2/BZip2+BlockSize.swift:11:5: warning: 'public' modifier is redundant for enum declared in a public extension
    public enum BlockSize: Int {
[53/54] Compiling NIO AddressedEnvelope.swift
[54/58] Compiling NIOFoundationCompat ByteBuffer-foundation.swift
[55/58] Compiling NIOTLS ApplicationProtocolNegotiationHandler.swift
[56/59] Compiling Bits BitsError.swift
[57/59] Compiling Async Async+NIO.swift
[58/60] Compiling NIOOpenSSL ByteBufferBIO.swift
[59/60] Compiling NIOHTTP1 ByteCollectionUtils.swift
[60/63] Compiling Random Array+Random.swift
[61/63] Compiling NIOWebSocket Base64.swift
[62/63] Compiling Core BasicKey.swift
/app/.build/checkouts/core/Sources/Core/Process+Execute.swift:163:17: warning: 'launchPath' is deprecated: renamed to 'executableURL'
        process.launchPath = path
/app/.build/checkouts/core/Sources/Core/Process+Execute.swift:163:17: note: use 'executableURL' instead
        process.launchPath = path
/app/.build/checkouts/core/Sources/Core/Process+Execute.swift:167:17: warning: 'launch()' is deprecated: renamed to 'run'
/app/.build/checkouts/core/Sources/Core/Process+Execute.swift:167:17: note: use 'run' instead
[63/67] Compiling Validation Exports.swift
[64/67] Compiling Service Config.swift
[65/67] Compiling Multipart Exports.swift
[66/68] Compiling Logging Exports.swift
[67/69] Compiling URLEncodedForm URLEncodedFormDecoder.swift
[68/72] Compiling Routing Parameter.swift
[69/73] Compiling Crypto BCryptDigest.swift
[70/74] Compiling DatabaseKit Container+CachedConnection.swift
[71/75] Compiling HTTP HTTPBody.swift
[72/76] Compiling Console ActivityBar.swift
[73/77] Compiling TemplateKit TemplateConditional.swift
[74/78] Compiling WebSocket Exports.swift
/app/.build/checkouts/websocket/Sources/WebSocket/WebSocketHandler.swift:37:13: warning: variable 'frame' was never mutated; consider changing to 'let' constant
        var frame = self.unwrapInboundIn(data)
        ~~~ ^
[75/78] Compiling Command CommandOption.swift
[76/80] Compiling SQL Exports.swift
[77/81] Compiling Redis RedisChannelData.swift
[78/81] Compiling Fluent CacheEntry.swift
[79/82] Compiling FluentSQL Exports.swift
[80/82] Compiling Vapor Application.swift
[81/82] Compiling PostgreSQL PostgreSQLDataDecoder.swift
[82/83] Compiling FluentPostgreSQL Deprecated.swift
[83/84] Compiling App AdminAuthorizationRequiredMiddleware.swift
/app/Sources/App/Notifications/SafariPushClient.swift:74:33: error: use of undeclared type 'PKCS12'
                var p12: UnsafeMutablePointer<PKCS12>?
/app/Sources/App/Notifications/SafariPushClient.swift:87:34: error: use of undeclared type 'EVP_PKEY'
                var pkey: UnsafeMutablePointer<EVP_PKEY>?
/app/Sources/App/Notifications/SafariPushClient.swift:88:34: error: use of undeclared type 'X509'
                var cert: UnsafeMutablePointer<X509>?
/app/Sources/App/Notifications/SafariPushClient.swift:89:32: error: use of undeclared type 'stack_st_X509'
                var ca: UnsafeMutablePointer<stack_st_X509>?
ERROR: Service 'app' failed to build: The command '/bin/sh -c swift build -c release' returned a non-zero code: 1

Thanks! You’re using the OpenSSL 1.1 headers where all types are made opaque (forward declared C struct pointers). Swift imports opaque C types as OpaquePointer and not UnsafePointer<struct>. This unfortunately means that whilst in C you can write programs that work fine against OpenSSL 1.0 & 1.1, in Swift this is very hard (because OpenSSL 1.0’s types import as UnsafePointer<struct> and 1.1’s types as OpaquePointer).

You have basically 2 options:

  • install the OpenSSL 1.0 headers
  • use the OpenSSL 1.1 API which in Swift means OpaquePointer everywhere :frowning: (for more information on that topic check this Opaque Pointers in Swift )

If you want a quick win, you might want to switch to Ubuntu 16.04 which defaults to OpenSSL 1.0 everywhere. Sad but easy :grimacing:

Honestly, I'd do whatever is least disruptive — which I think would be the latter. The function that does the signature generation is only about 100 lines total, so managing OpaquePointer is feasible.

Which then brings me onto — how do I actually do that? What should I import and how do I make Xcode see the 'opaque' versions instead (so my dev environment matches)?

I assume on the Mac you installed OpenSSL with homebrew? If you choose to go OpenSSL 1.1 everywhere, you should remove the openssl you have right now and brew install openssl@1.1 so you get 1.1 on the Mac too. But as I said above, 1.0 everywhere (ie Ubuntu 16.04) might be a better bang for the buck until we have a better cross platform crypto story...

Some more info also here OpenSSL errors - #2 by johannesweiss .

I have 1.1 installed through Homebrew but Xcode seems to still be picking up the system LibreSSL (which I don't think I can remove).

Do I have to pass linker flags to SwiftPM somewhere?

(EDIT: Don't do this. See later messages in thread.)

Aha! I found the spot in Package.swift to tie everything up:

			cSettings: [
				.unsafeFlags(["-I/usr/local/opt/openssl@1.1/include"], .when(platforms: [.macOS]))
			linkerSettings: [
				.unsafeFlags(["-L/usr/local/opt/openssl@1.1/lib"], .when(platforms: [.macOS]))

@bzamayo you shouldn't need to use unsafeFlags (and in fact it won't work with libraries that depend on you). For homebrew, you also need to brew install pkg-config. Also, you should tell homebrew which SSL library to use, this can be done either by force linking (not recommended) or by putting

export PKG_CONFIG_PATH="/usr/local/opt/openssl@1.1/lib/pkgconfig"

into your shell init file (.bashrc or so). Homebrew should have told you about the instructions when installing openssl@1.1.

The reason why it's still picking up OpenSSL 1.0 may be that you force linked OpenSSL into your system. You can check that by doing ls -la /usr/local/include/ssl and see what that says. It should say 'no such file or directory'.

The pkg-config stuff is what I tried first, but it didn't seem to affect the app build. I'll try again though.

You should be able to check if it works this way:

$ pkg-config --libs --cflags libssl libcrypto
-I/Users/johannes/.brew/Cellar/openssl@1.1/1.1.1d/include -L/Users/johannes/.brew/Cellar/openssl@1.1/1.1.1d/lib -lssl -lcrypto

(note: I use a non-standard brew --prefix (~/.brew instead of /usr/local) so things will look slightly different for you.)

The output of that command looks correct (I didn't change anything), so I uncommented the unsafeFlags and huzzah, everything compiles! I guess some cache or something was interfering yesterday ...

Anyway, thank you again for your help here!

1 Like