Skip to content
ecdsa.com
C · Command line

GnuTLS

GnuTLS, the GNU project's TLS and X.509 library: DER ECDSA on the NIST curves, RFC 6979 through a flag, Ed25519 and Ed448 — and, in the distribution build checked, no secp256k1 and no working post-quantum algorithms.
License
LGPL-2.1-or-later
Checked version
3.8.13 ·
Source
gitlab.com/gnutls/gnutls
Documentation
www.gnutls.org/manual/

Compatibility at a glance

ECDSAEdDSA

ECDSA curves
P-224P-256P-384P-521
EdDSA
Ed25519Ed448

GnuTLS provides TLS, X.509 and PKCS handling for many GNU and Linux components and ships certtool and gnutls-cli. Its abstract gnutls_privkey_* and gnutls_pubkey_* interfaces work the same for software keys, PKCS#11 tokens and TPMs. The facts here were checked on the Alpine 3.24.2 package, GnuTLS 3.8.13, which uses Nettle for the arithmetic. Since GnuTLS 3.8.10, post-quantum algorithms come only from the optional leancrypto library, and this package does not link it.

gnutls_privkey_sign_data returns ECDSA signatures as DER with a random nonce; the flag GNUTLS_PRIVKEY_FLAG_REPRODUCIBLE switches to RFC 6979, and with it the RFC's P-256 test vector reproduced exactly. s is not normalized and high-S signatures verify. gnutls_privkey_sign_hash2 signs a digest you computed.

Verification returns a negative error code on failure, and every kind of failure looked the same: a raw r‖s signature, a 3-byte blob and a valid signature over other data all gave GNUTLS_E_PK_SIG_VERIFY_FAILED (-89). Use the DER ⇄ raw converter to rule out an encoding problem.

ECDSA is limited to the NIST curves P-224 to P-521 — gnutls_ecc_curve_get_id("SECP256K1") returned 0, unknown — while Ed25519 and Ed448 both signed and verified. The headers declare GNUTLS_PK_MLKEM768 and GNUTLS_PK_MLDSA65, but this build implements neither: they are missing from gnutls_pk_list(), ML-DSA-65 key generation fails with GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM, and the TLS groups listed by gnutls-cli --list include X25519 and X448 but no ML-KEM hybrid. Code that compiles against these constants can still fail at run time, so check the runtime list. Inspect certificates produced by certtool with the certificate decoder.

Facts and the checks behind them

Nothing below is copied from documentation. The check content/libraries/verify/gnutls/run.sh ran GnuTLS 3.8.13 in the container image alpine:3.24.2 on October 20, 2026; the page must match what it printed, and each fact lists those lines.

  • ECDSA signature encoding

    der

    gnutls_privkey_sign_data returns ASN.1 DER.

    Recorded by the check
    fact ecdsa-signature-encoding = der
  • ECDSA nonce

    rfc6979-opt-in

    Random by default; GNUTLS_PRIVKEY_FLAG_REPRODUCIBLE gave identical signatures and reproduced RFC 6979 appendix A.2.5.

    Recorded by the check
    fact ecdsa-nonce = rfc6979-opt-in
    evidence ecdsa-nonce: default repeats: no; GNUTLS_PRIVKEY_FLAG_REPRODUCIBLE repeats: yes and reproduces the RFC 6979 A.2.5 r: yes
  • Low-S when signing

    either

    s is not normalized: high-S values appeared among 64 signatures.

    Recorded by the check
    fact ecdsa-low-s-sign = either
  • High-S when verifying

    accepts-high-s

    A DER signature with s replaced by n − s verified.

    Recorded by the check
    fact ecdsa-low-s-verify = accepts-high-s
  • What the signing call hashes

    hashes-message-or-digest

    gnutls_privkey_sign_data hashes; gnutls_privkey_sign_hash2 signs a digest, and that signature verified against the message.

    Recorded by the check
    fact ecdsa-prehash = hashes-message-or-digest
  • Malformed ECDSA signature

    returns-false

    A raw r‖s signature, a 3-byte blob and a valid signature over other data all returned GNUTLS_E_PK_SIG_VERIFY_FAILED (-89).

    Recorded by the check
    fact ecdsa-malformed-signature = returns-false
    evidence ecdsa-malformed-signature: gnutls_pubkey_verify_data2: raw r||s -89 (GNUTLS_E_PK_SIG_VERIFY_FAILED), 3 bytes -89 (GNUTLS_E_PK_SIG_VERIFY_FAILED), wrong message -89 (GNUTLS_E_PK_SIG_VERIFY_FAILED)
  • ECDSA curves

    P-224P-256P-384P-521

    P-224, P-256, P-384 and P-521; SECP256K1 is not a known curve.

    Recorded by the check
    fact ecdsa-curves = P-224, P-256, P-384, P-521
    evidence ecdsa-curves: gnutls_ecc_curve_get_id("SECP256K1") = 0 (0 = unknown)
  • EdDSA

    Ed25519Ed448

    Ed25519 and Ed448 keys generated, signed and verified.

    Recorded by the check
    fact eddsa = Ed25519, Ed448
    evidence eddsa: Ed25519: keygen GNUTLS_E_SUCCESS, sign ok, verify ok
    evidence eddsa: Ed448: keygen GNUTLS_E_SUCCESS, sign ok, verify ok
  • Post-quantum algorithms

    none

    ML-DSA and ML-KEM are declared in the headers but absent from gnutls_pk_list(); ML-DSA-65 key generation failed, and X25519-MLKEM768 is an unknown group.

    Recorded by the check
    fact pqc = none
    evidence pqc: the headers declare GNUTLS_PK_MLKEM768 = 14 and GNUTLS_PK_MLDSA65 = 16; gnutls_pk_list(): RSA, RSA-PSS, RSA-OAEP, RSA, DSA, GOST R 34.10-2012-512, GOST R 34.10-2012-256, GOST R 34.10-2001, EC/ECDSA, EdDSA (Ed25519), EdDSA (Ed448), DH, ECDH (X25519), ECDH (X448)
    evidence pqc: ML-DSA-65: in gnutls_pk_list: no; key generation GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM, sign and verify failed
    evidence pqc: ML-KEM-768: in gnutls_pk_list: no; gnutls_group_get_id("X25519-MLKEM768") = 0 (0 = unknown)
    evidence pqc: gnutls-cli --list Groups: GROUP-SECP192R1, GROUP-SECP224R1, GROUP-SECP256R1, GROUP-SECP384R1, GROUP-SECP521R1, GROUP-X25519, GROUP-GC256B, GROUP-GC512A, GROUP-X448, GROUP-FFDHE2048, GROUP-FFDHE3072, GROUP-FFDHE4096, GROUP-FFDHE6144, GROUP-FFDHE8192
    evidence pqc: shared libraries the gnutls package depends on: libbrotlidec.so.1 libbrotlienc.so.1 libc.musl-aarch64.so.1 libgmp.so.10 libhogweed.so.6 libidn2.so.0 libnettle.so.8 libp11-kit.so.0 libtasn1.so.6 libunistring.so.5 libz.so.1 libzstd.so.1 (GnuTLS 3.8.10 NEWS: PQC is only provided through leancrypto)

Usage

Generate a P-256 key, sign twice with the reproducible flag and verify, checking that the signatures are identical.
#include <gnutls/abstract.h>
#include <gnutls/gnutls.h>
#include <stdio.h>
#include <string.h>

int main(void) {
    gnutls_global_init();
    gnutls_privkey_t priv;
    gnutls_pubkey_t pub;
    gnutls_privkey_init(&priv);
    gnutls_privkey_generate(priv, GNUTLS_PK_ECDSA, GNUTLS_CURVE_TO_BITS(GNUTLS_ECC_CURVE_SECP256R1), 0);
    gnutls_pubkey_init(&pub);
    gnutls_pubkey_import_privkey(pub, priv, 0, 0);

    gnutls_datum_t msg = {(unsigned char *)"order #1042 approved", 20}, sig1, sig2;
    /* REPRODUCIBLE switches ECDSA to RFC 6979 nonces; the default is random. */
    gnutls_privkey_sign_data(priv, GNUTLS_DIG_SHA256, GNUTLS_PRIVKEY_FLAG_REPRODUCIBLE, &msg, &sig1);
    gnutls_privkey_sign_data(priv, GNUTLS_DIG_SHA256, GNUTLS_PRIVKEY_FLAG_REPRODUCIBLE, &msg, &sig2);

    /* Negative return codes mean failure; any malformed input is GNUTLS_E_PK_SIG_VERIFY_FAILED. */
    int rc = gnutls_pubkey_verify_data2(pub, GNUTLS_SIGN_ECDSA_SHA256, 0, &msg, &sig1);
    printf("verified: %d, deterministic: %d\n", rc >= 0, sig1.size == sig2.size && memcmp(sig1.data, sig2.data, sig1.size) == 0);
    gnutls_global_deinit();
    return 0;
}

The snippet harness runs every sample verbatim in the container image alpine:3.24.2, and the run fails unless the output matches.

Setup
apk add --no-cache -q build-base gnutls-dev=3.8.13-r0 >/dev/null
Command
cc -o usage usage.c -lgnutls && ./usage
Output must match (regular expression)
verified: 1, deterministic: 1

Libraries