Privacy Tools Guide

How to Set Up GnuTLS for Application Security

GnuTLS is the TLS library preferred on GNU/Linux systems (used by GLib, curl, libsoup, and GNOME applications). It has a different API philosophy from OpenSSL — credential-based rather than context-based — and excellent support for TLS 1.3, DTLS, and mutual certificate authentication. This guide covers practical integration in C and Python, certificate pinning, and system-level hardening.

Install GnuTLS

# Ubuntu / Debian
sudo apt install -y libgnutls28-dev gnutls-bin gnutls-doc python3-gnutls

# RHEL / Fedora
sudo dnf install -y gnutls gnutls-devel gnutls-utils

# Verify version (3.8+ recommended for TLS 1.3 support)
gnutls-cli --version

Generate Certificates with certtool

GnuTLS ships certtool, which is more scriptable than OpenSSL’s CA workflow:

# Generate a private key (Ed25519 for modern security)
certtool --generate-privkey --key-type ed25519 --outfile server-key.pem

# Generate a self-signed certificate for testing
certtool --generate-self-signed --load-privkey server-key.pem \
  --outfile server-cert.pem <<'EOF'
Country name: US
State: CA
Locality: San Francisco
Organization: Example Corp
Common name: server.example.com
Does the certificate belong to an authority? no
Is this a TLS web server certificate? yes
Enter the dnsName of the subject of the certificate: server.example.com
Is the above information ok? yes
EOF

# Generate CA + signed server cert (production pattern)
certtool --generate-privkey --key-type rsa --bits 4096 --outfile ca-key.pem
certtool --generate-self-signed --load-privkey ca-key.pem \
  --template ca.cfg --outfile ca-cert.pem

# ca.cfg template:
cat > ca.cfg <<'EOF'
cn = "Example CA"
ca
cert_signing_key
expiration_days = 3650
EOF

certtool --generate-privkey --key-type ed25519 --outfile app-key.pem
certtool --generate-request --load-privkey app-key.pem \
  --template server.cfg --outfile app-req.pem

certtool --generate-certificate \
  --load-request app-req.pem \
  --load-ca-certificate ca-cert.pem \
  --load-ca-privkey ca-key.pem \
  --template server.cfg --outfile app-cert.pem

Command-Line TLS Testing

# Connect to a server and show certificate details
gnutls-cli --print-cert server.example.com

# Test with specific TLS version
gnutls-cli --priority "NORMAL:-VERS-TLS-ALL:+VERS-TLS1.3" server.example.com

# Test with mutual TLS (client presents a certificate)
gnutls-cli --x509certfile client-cert.pem --x509keyfile client-key.pem \
  server.example.com

# Verify a certificate against a CA bundle
gnutls-cli --x509cafile ca-cert.pem --no-system-cas server.example.com

# Check supported cipher suites for a server
gnutls-cli --list server.example.com

C Integration: Minimal TLS Client

/* tls_client.c — GnuTLS 3.x minimal client */
#include <gnutls/gnutls.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>

#define SERVER   "server.example.com"
#define PORT     "443"
#define CAFILE   "/etc/ssl/certs/ca-certificates.crt"

int tcp_connect(const char *host, const char *port) {
    struct addrinfo hints = {0}, *res;
    hints.ai_socktype = SOCK_STREAM;
    getaddrinfo(host, port, &hints, &res);
    int fd = socket(res->ai_family, res->ai_socktype, 0);
    connect(fd, res->ai_addr, res->ai_addrlen);
    freeaddrinfo(res);
    return fd;
}

int main(void) {
    gnutls_global_init();

    gnutls_certificate_credentials_t creds;
    gnutls_certificate_allocate_credentials(&creds);

    /* Load system CA bundle */
    gnutls_certificate_set_x509_trust_file(creds, CAFILE, GNUTLS_X509_FMT_PEM);

    /* Set TLS 1.3 only with strong ciphers */
    gnutls_session_t session;
    gnutls_init(&session, GNUTLS_CLIENT);
    gnutls_priority_set_direct(session,
        "SECURE256:+VERS-TLS1.3:-VERS-TLS-ALL", NULL);

    gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, creds);
    gnutls_server_name_set(session, GNUTLS_NAME_DNS, SERVER, strlen(SERVER));

    /* Connect and hand off to GnuTLS */
    int fd = tcp_connect(SERVER, PORT);
    gnutls_transport_set_int(session, fd);
    gnutls_handshake_set_timeout(session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);

    int ret;
    do { ret = gnutls_handshake(session); }
    while (ret < 0 && gnutls_error_is_fatal(ret) == 0);

    if (ret < 0) {
        fprintf(stderr, "Handshake failed: %s\n", gnutls_strerror(ret));
        goto cleanup;
    }

    /* Verify certificate */
    unsigned int status;
    gnutls_certificate_verify_peers3(session, SERVER, &status);
    if (status != 0) {
        gnutls_datum_t out;
        gnutls_certificate_verification_status_print(
            status, GNUTLS_CRT_X509, &out, 0);
        fprintf(stderr, "Certificate verification failed: %s\n", out.data);
        gnutls_free(out.data);
        goto cleanup;
    }

    printf("TLS version: %s\n", gnutls_protocol_get_name(
           gnutls_protocol_get_version(session)));
    printf("Cipher: %s\n", gnutls_cipher_get_name(
           gnutls_cipher_get(session)));

    /* Send HTTP GET */
    const char *req = "GET / HTTP/1.1\r\nHost: " SERVER "\r\nConnection: close\r\n\r\n";
    gnutls_record_send(session, req, strlen(req));

    char buf[4096];
    while ((ret = gnutls_record_recv(session, buf, sizeof(buf)-1)) > 0) {
        buf[ret] = '\0';
        printf("%s", buf);
    }

cleanup:
    gnutls_bye(session, GNUTLS_SHUT_RDWR);
    close(fd);
    gnutls_deinit(session);
    gnutls_certificate_free_credentials(creds);
    gnutls_global_deinit();
    return 0;
}
gcc -o tls_client tls_client.c $(pkg-config --cflags --libs gnutls)
./tls_client

Python Integration with python-gnutls

from gnutls.library.functions import *
from gnutls.crypto import *
import socket

# Simpler approach: use Python's ssl module (backed by GnuTLS on Debian/Ubuntu)
import ssl

def create_gnutls_context(cafile=None, certfile=None, keyfile=None,
                          pin=None):
    """
    Create an SSL context using GnuTLS as the backend.
    pin: SHA-256 hex fingerprint of the expected server certificate (optional).
    """
    ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
    ctx.minimum_version = ssl.TLSVersion.TLSv1_3
    ctx.verify_mode = ssl.CERT_REQUIRED
    ctx.check_hostname = True

    # Strong cipher list (GnuTLS priority string via OpenSSL compat)
    ctx.set_ciphers("TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256")

    if cafile:
        ctx.load_verify_locations(cafile)
    else:
        ctx.load_default_certs()

    if certfile and keyfile:
        ctx.load_cert_chain(certfile, keyfile)

    return ctx, pin

def connect_with_pin(host, port, ctx, expected_pin=None):
    with socket.create_connection((host, port)) as raw_sock:
        with ctx.wrap_socket(raw_sock, server_hostname=host) as tls_sock:
            if expected_pin:
                import hashlib
                cert_der = tls_sock.getpeercert(binary_form=True)
                actual_pin = hashlib.sha256(cert_der).hexdigest()
                if actual_pin != expected_pin:
                    raise ssl.SSLCertVerificationError(
                        f"Certificate pin mismatch: expected {expected_pin}, "
                        f"got {actual_pin}"
                    )
            return tls_sock.version(), tls_sock.cipher()

# Usage
ctx, pin = create_gnutls_context(
    pin="a3b4c5d6e7..."   # SHA-256 of expected server cert DER
)
version, cipher = connect_with_pin("server.example.com", 443, ctx, pin)
print(f"Connected: {version} / {cipher[0]}")

Priority Strings Reference

GnuTLS uses priority strings to configure allowed algorithms:

# TLS 1.3 only, strong ciphers
"SECURE256:+VERS-TLS1.3:-VERS-TLS-ALL"

# All modern versions (1.2 + 1.3), no weak ciphers
"NORMAL:-3DES-CBC:-RC4-128"

# mTLS: require client certificate
"NORMAL:%REQUIRE_CRT_IN_HANDSHAKE"

# FIPS-compatible
"FIPS140-2"

# Test what a priority string enables
gnutls-cli --list --priority "SECURE256"

Setting System-Wide GnuTLS Policy

On Fedora/RHEL with system crypto policies:

# View current policy
update-crypto-policies --show

# Upgrade to FUTURE policy (TLS 1.2+, RSA 3072+)
sudo update-crypto-policies --set FUTURE

# Or set custom policy
sudo tee /etc/crypto-policies/policies/custom.pol > /dev/null <<'EOF'
min_tls_version = TLS1.2
min_dtls_version = DTLS1.2
key_size = RSA >= 3072
hash = SHA2-256+
EOF

sudo update-crypto-policies --set CUSTOM


Built by theluckystrike — More at zovo.one