Introduction to cryptographic libraries¶
The cryptographic library landscape is vast and complex, and there are many crypto libraries available on an Ubuntu system. What an application developer decides to use can be governed by many aspects, such as:
Technical requirements
Language bindings
License
Community
Ease of use
General availability
Upstream maintenance
Among the most popular and widely used libraries and frameworks, we have:
OpenSSL
GnuTLS
NSS
GnuPG
gcrypt
Each one of these has its own implementation details, API, behavior, configuration file, and syntax.
This poses a challenge to system administrators who need to determine what cryptographic algorithms are being used on the systems they deploy. How does one ensure no legacy crypto is being used? Or that no keys below a certain size are ever selected or created? And which types of X509 certificates are acceptable for connecting to remote servers?
One has to check all of the crypto implementations installed on the system and their configuration. To make things even more complicated, sometimes an application implements its own crypto, without using anything external.
How do we know which library an application is using?¶
Ultimately, the only reliable way to determine how an application uses cryptography is via its documentation or inspection of source code. But the code is not always available, and sometimes the documentation lacks this information. When the documentation isn’t clear or enough, there are some other practical checks that can be made.
First, let’s take a look at how an application could use crypto.
Dynamic linking¶
This is the most common way, and very easy to spot via package dependencies and helper tools. This is discussed later in this page.
Static linking¶
This is harder, as there is no dependency information in the binary package, and this usually requires inspection of the source package to see Build Dependencies. An example is shown later in this page.
Plugins¶
The main binary of an application can not depend directly on a crypto library, but it could load dynamic plugins which do. Usually these would be packaged separately, and then we fall under the dynamic or static linking cases above. Note that via such a plugin mechanism, an application could depend on multiple external cryptographic libraries.
Execution of external binary¶
The application could just plain call external binaries at runtime for its cryptographic operations, like calling out to openssl
or gnupg
to encrypt/decrypt data. This will hopefully be expressed in the dependencies of the package. If it’s not, then it’s a bug that should be reported.
Indirect usage¶
The application could be using a third party library or executable which in turn could fall into any of the above categories.
Identify the crypto libraries used by an application¶
Here are some tips that can help identifying the crypto libraries used by an application that is installed on an Ubuntu system:
Documentation¶
Read the application documentation. It might have crypto options directly in its own configuration files, or point at specific crypto configuration files installed on the system. This may also clarify if the application even uses external crypto libraries, or if it has its own implementation.
Package dependencies¶
The package dependencies are a good way to check what is needed at runtime by the application.
To find out the package that owns a file, use dpkg -S
. For example:
$ dpkg -S /usr/bin/lynx
lynx: /usr/bin/lynx
Then, with the package name in hand, check its dependencies. It’s best to also look for Recommends
, as they are installed by default. Continuing with the example from before, we have:
$ dpkg -s lynx | grep -E "^(Depends|Recommends)"
Depends: libbsd0 (>= 0.0), libbz2-1.0, libc6 (>= 2.34), libgnutls30 (>= 3.7.0), libidn2-0 (>= 2.0.0), libncursesw6 (>= 6), libtinfo6 (>= 6), zlib1g (>= 1:1.1.4), lynx-common
Recommends: mime-support
Here we see that lynx
links with libgnutls30
, which answers our question: lynx
uses the GnuTLS library for its cryptography operations.
Dynamic linking, plugins¶
The dynamic libraries that are needed by an application should always be correctly identified in the list of dependencies of the application package. When that is not the case, or if you need to identify what is needed by some plugin that is not part of the package, you can use some system tools to help identify the dependencies.
A very helpful tool that is installed in all Ubuntu systems is ldd
. It will list all the dynamic libraries that are needed by the given binary, including dependencies of dependencies, i.e. it’s recursive. Going back to the lynx
example:
$ ldd /usr/bin/lynx
linux-vdso.so.1 (0x00007ffffd2df000)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007feb69d77000)
libbz2.so.1.0 => /lib/x86_64-linux-gnu/libbz2.so.1.0 (0x00007feb69d64000)
libidn2.so.0 => /lib/x86_64-linux-gnu/libidn2.so.0 (0x00007feb69d43000)
libncursesw.so.6 => /lib/x86_64-linux-gnu/libncursesw.so.6 (0x00007feb69d07000)
libtinfo.so.6 => /lib/x86_64-linux-gnu/libtinfo.so.6 (0x00007feb69cd5000)
libgnutls.so.30 => /lib/x86_64-linux-gnu/libgnutls.so.30 (0x00007feb69aea000)
libbsd.so.0 => /lib/x86_64-linux-gnu/libbsd.so.0 (0x00007feb69ad0000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007feb698a8000)
libunistring.so.2 => /lib/x86_64-linux-gnu/libunistring.so.2 (0x00007feb696fe000)
libp11-kit.so.0 => /lib/x86_64-linux-gnu/libp11-kit.so.0 (0x00007feb695c3000)
libtasn1.so.6 => /lib/x86_64-linux-gnu/libtasn1.so.6 (0x00007feb695ab000)
libnettle.so.8 => /lib/x86_64-linux-gnu/libnettle.so.8 (0x00007feb69565000)
libhogweed.so.6 => /lib/x86_64-linux-gnu/libhogweed.so.6 (0x00007feb6951b000)
libgmp.so.10 => /lib/x86_64-linux-gnu/libgmp.so.10 (0x00007feb69499000)
/lib64/ld-linux-x86-64.so.2 (0x00007feb69fe6000)
libmd.so.0 => /lib/x86_64-linux-gnu/libmd.so.0 (0x00007feb6948c000)
libffi.so.8 => /lib/x86_64-linux-gnu/libffi.so.8 (0x00007feb6947f000)
We again see the GnuTLS library (via libgnutls.so.30
) in the list, and can reach the same conclusion.
Another way to check for such dependencies, but without the recursion, is via objdump
. This may need to be installed via the binutils
package, as it’s not mandatory.
The way to use it is to grep for the NEEDED
string:
$ objdump -x /usr/bin/lynx|grep NEEDED
NEEDED libz.so.1
NEEDED libbz2.so.1.0
NEEDED libidn2.so.0
NEEDED libncursesw.so.6
NEEDED libtinfo.so.6
NEEDED libgnutls.so.30
NEEDED libbsd.so.0
NEEDED libc.so.6
Finally, if you want to see the dependency tree, you can use lddtree
from the pax-utils
package:
$ lddtree /usr/bin/lynx
lynx => /usr/bin/lynx (interpreter => /lib64/ld-linux-x86-64.so.2)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1
libbz2.so.1.0 => /lib/x86_64-linux-gnu/libbz2.so.1.0
libidn2.so.0 => /lib/x86_64-linux-gnu/libidn2.so.0
libunistring.so.2 => /lib/x86_64-linux-gnu/libunistring.so.2
libncursesw.so.6 => /lib/x86_64-linux-gnu/libncursesw.so.6
libtinfo.so.6 => /lib/x86_64-linux-gnu/libtinfo.so.6
libgnutls.so.30 => /lib/x86_64-linux-gnu/libgnutls.so.30
libp11-kit.so.0 => /lib/x86_64-linux-gnu/libp11-kit.so.0
libffi.so.8 => /lib/x86_64-linux-gnu/libffi.so.8
libtasn1.so.6 => /lib/x86_64-linux-gnu/libtasn1.so.6
libnettle.so.8 => /lib/x86_64-linux-gnu/libnettle.so.8
libhogweed.so.6 => /lib/x86_64-linux-gnu/libhogweed.so.6
libgmp.so.10 => /lib/x86_64-linux-gnu/libgmp.so.10
ld-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2
libbsd.so.0 => /lib/x86_64-linux-gnu/libbsd.so.0
libmd.so.0 => /lib/x86_64-linux-gnu/libmd.so.0
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6
Static linking¶
Identifying which libraries were used in a static build is a bit more involved. There are two ways, and they are complementary most of the time:
look for the
Built-Using
header in the binary packageinspect the
Build-Depends
header in the source package
For example, let’s try to discover which crypto libraries, if any, the rclone
tool uses. First, let’s try the packaging dependencies:
$ dpkg -s rclone | grep -E "^(Depends|Recommends)"
Depends: libc6 (>= 2.34)
Uh, that’s a short list. But rclone
definitely supports encryption, so what is going on? Turns out this is a tool written in the Go language, and that uses static linking of libraries. So let’s try to inspect the package data more carefully, and this time look for the Built-Using
header:
$ dpkg -s rclone | grep Built-Using
Built-Using: go-md2man-v2 (= 2.0.1+ds1-1), golang-1.18 (= 1.18-1ubuntu1), golang-bazil-fuse (= 0.0~git20160811.0.371fbbd-3), ...
Ok, this time we have a lot of information (truncated above for brevity, since it’s all in one very long line). If we look at the full output carefully, we can see that rclone
was built statically using the golang-go.crypto
package, and documentation about that package and its crypto implementations is what we should look for.
If the Built-Using
header was not there, or didn’t yield any clues, we could try one more step and look for the build dependencies. These can be found in the debian/control
file of the source package. In the case of rclone
for Ubuntu Jammy, that can be seen at https://git.launchpad.net/ubuntu/+source/rclone/tree/debian/control?h=ubuntu/jammy-devel#n7, and a quick look at the Build-Depends
list shows us the golang-golang-x-crypto-dev
build dependency, whose source package is golang-go.crypto
as expected:
$ apt-cache show golang-golang-x-crypto-dev | grep ^Source:
Source: golang-go.crypto
NOTE If there is no
Source:
line, then it means the name of the source package is the same as the binary package that was queried.
What’s next?¶
Now that you have uncovered which library your application is using, the following guides will help you to understand the associated configuration files and what options you have available (including some handy examples!).