GPG and openssl and curl and OSX

Those playing along at home may remember pain with GPG well, that appears to have gotten more annoying.

What?

Libcurl, and gpg2 and openssl… Or so I assumed.

[laptop:~]% gpg2 --verbose --keyserver-options=debug,verbose --search foo
gpg: searching for "foo" from hkps server hkps.pool.sks-keyservers.net
gpgkeys: curl version = libcurl/7.35.0 SecureTransport zlib/1.2.5
gpgkeys: search type is 0, and key is "foo"
* Hostname was NOT found in DNS cache
*   Trying 209.135.211.141...
* Connected to hkps.pool.sks-keyservers.net (209.135.211.141) port 443 (#0)
* SSL certificate problem: Invalid certificate chain
* Closing connection 0
gpgkeys: HTTP search error 60: SSL certificate problem: Invalid certificate chain
gpg: key "foo" not found on keyserver
gpg: keyserver internal error
gpg: keyserver search failed: Keyserver error

But… that key is right?

[laptop:~]% openssl s_client -CAfile ~/.gnupg/sks-keyservers.netCA.pem -verify 6 \
            -connect hkps.pool.sks-keyservers.net:443 -servername hkps.pool.sks-keyservers.net
verify depth is 6
CONNECTED(00000003)
depth=1 C = NO, ST = Oslo, O = sks-keyservers.net CA, CN = sks-keyservers.net CA
verify return:1
depth=0 C = NO, ST = Oslo, O = keys2.kfwebs.net, CN = keys2.kfwebs.net
verify return:1
---
Certificate chain
 0 s:/C=NO/ST=Oslo/O=keys2.kfwebs.net/CN=keys2.kfwebs.net
   i:/C=NO/ST=Oslo/O=sks-keyservers.net CA/CN=sks-keyservers.net CA
 1 s:/C=NO/ST=Oslo/O=sks-keyservers.net CA/CN=sks-keyservers.net CA
   i:/C=NO/ST=Oslo/O=sks-keyservers.net CA/CN=sks-keyservers.net CA
---
[snip...]

So wait, openssl says it’s fine, but when I use my homebrew gpg2, that pulls in libcurl, it should support SNI, right?

[laptop:~]% otool -L /usr/local/Cellar/gnupg2/2.0.22/libexec/gpg2keys_hkp                                                         1
/usr/local/Cellar/gnupg2/2.0.22/libexec/gpg2keys_hkp:
    /usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 1.0.0)
    /usr/local/lib/libgpg-error.0.dylib (compatibility version 11.0.0, current version 11.0.0)
    /usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)
    /usr/local/opt/curl/lib/libcurl.4.dylib (compatibility version 8.0.0, current version 8.0.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1197.1.1)

So that’s using the libcurl that I pulled in via homebrew, so that’s sane, right…

[laptop:~]% otool -L /usr/local/opt/curl/lib/libcurl.4.dylib                                                                      1
/usr/local/opt/curl/lib/libcurl.4.dylib:
    /usr/local/opt/curl/lib/libcurl.4.dylib (compatibility version 8.0.0, current version 8.0.0)
    /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 855.11.0)
    /System/Library/Frameworks/Security.framework/Versions/A/Security (compatibility version 1.0.0, current version 55471.0.0)
    /System/Library/Frameworks/LDAP.framework/Versions/A/LDAP (compatibility version 1.0.0, current version 2.4.0)
    /usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.5)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1197.1.1)

Umm, what? It’s not using openssl, it’s using Security.framework! A quick ‘brew info curl’

[laptop:~]% brew options curl
--with-ares
    Build with C-Ares async DNS support
--with-gssapi
    Build with GSSAPI/Kerberos authentication support.
--with-libmetalink
    Build with libmetalink support
--with-openssl
    Build with OpenSSL instead of Secure Transport
--with-ssh
    Build with scp and sftp support

Oh, so I can build it with openssl support and not Security.framework…

If I import the sks-keyservers.netCA.pem cert in to Keychain.app, and try again:

[laptop:~]% gpg2 --verbose --keyserver-options=debug,verbose --search foo
gpg: searching for "foo" from hkps server hkps.pool.sks-keyservers.net
gpgkeys: curl version = libcurl/7.35.0 SecureTransport zlib/1.2.5
gpgkeys: search type is 0, and key is "foo"
* Hostname was NOT found in DNS cache
*   Trying 208.89.139.251...
* Connected to hkps.pool.sks-keyservers.net (208.89.139.251) port 443 (#0)
* TLS 1.2 connection using TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
* Server certificate: sks.mrball.net
* Server certificate: sks-keyservers.net CA
> GET /pks/lookup?op=index&options=mr&search=foo HTTP/1.1
Host: hkps.pool.sks-keyservers.net

Oh look, it works. I thank @mrtazz for pair programming with me. (this is mostly a brain dumb, but if you can’t gpg, this may help!)

Update!

Yeah, there’s STILL MORE BROKEN! So once I realised that the issue wasn’t quite that simple. Gnupg was using gpg2keys_curl, which was using libcurl. The libcurl it was using was the system libcurl, which uses Security.Framework!

[laptop:~]% otool -L /usr/local/Cellar/gnupg2/2.0.22_1/libexec/* | grep -Ei 'curl|ssl'
/usr/local/Cellar/gnupg2/2.0.22_1/libexec/gpg2keys_curl:
    /usr/lib/libcurl.4.dylib (compatibility version 7.0.0, current version 8.0.0)
    /usr/lib/libcurl.4.dylib (compatibility version 7.0.0, current version 8.0.0)

So I had to build a homebrew libcurl with openssl (which I already had) and then make gpg use that libcurl, rather than system libcurl.

[laptop:Formula]% git diff
diff --git i/Library/Formula/gnupg2.rb w/Library/Formula/gnupg2.rb
index 3dc14e4..4b7c4f1 100644
--- i/Library/Formula/gnupg2.rb
+++ w/Library/Formula/gnupg2.rb
@@ -51,6 +51,7 @@ class Gnupg2 < Formula
     if build.with? 'readline'
       args << "--with-readline=#{Formula["readline"].opt_prefix}"
     end
+    args << "--with-libcurl=#{Formula["curl"].opt_prefix}"

     system "./configure", *args
     system "make"

Then rebuild gnupg2 to and it will use the libcurl from homebrew, which will then use openssl from homebrew, which you can then specify certs to!

Remind me again why no one uses gnupg?