SSL_CTX_DANE_ENABLE(3SSL)

okay bestie πŸ’– buckle up, this is SSL_CTX_DANE_ENABLE(3SSL) from the OpenSSL manual β€” the page that teaches your local TLS client how to do DANE authentication of the remote TLS server. it's long, it's important, and we're doing it in parts. let's go πŸ¦‹

🌸 NAME 🌸

the whole squad, roll call πŸ“£

  • SSL_CTX_dane_enable
  • SSL_CTX_dane_mtype_set
  • SSL_dane_enable
  • SSL_dane_tlsa_add
  • SSL_get0_dane_authority
  • SSL_get0_dane_tlsa
  • SSL_CTX_dane_set_flags
  • SSL_CTX_dane_clear_flags
  • SSL_dane_set_flags
  • SSL_dane_clear_flags

and together they do exactly one (1) job, which is: enable DANE TLS authentication of the remote TLS server in the local TLS client. that's it. that's the whole gig. πŸ’…


πŸ“œ SYNOPSIS πŸ“œ

#include <openssl/ssl.h>

int SSL_CTX_dane_enable(SSL_CTX *ctx);
int SSL_CTX_dane_mtype_set(SSL_CTX *ctx, const EVP_MD *md,
                           uint8_t mtype, uint8_t ord);
int SSL_dane_enable(SSL *s, const char *basedomain);
int SSL_dane_tlsa_add(SSL *s, uint8_t usage, uint8_t selector,
                      uint8_t mtype, const unsigned char *data, size_t dlen);
int SSL_get0_dane_authority(SSL *s, X509 **mcert, EVP_PKEY **mspki);
int SSL_get0_dane_tlsa(SSL *s, uint8_t *usage, uint8_t *selector,
                       uint8_t *mtype, const unsigned char **data,
                       size_t *dlen);
unsigned long SSL_CTX_dane_set_flags(SSL_CTX *ctx, unsigned long flags);
unsigned long SSL_CTX_dane_clear_flags(SSL_CTX *ctx, unsigned long flags);
unsigned long SSL_dane_set_flags(SSL *ssl, unsigned long flags);
unsigned long SSL_dane_clear_flags(SSL *ssl, unsigned long flags);

πŸ’… DESCRIPTION πŸ’…

okay so these functions are the whole DANE TLSA support system β€” as in RFC6698 and RFC7671 peer authentication. no cap, that's what we're doing here. ✨

πŸ’– SSL_CTX_dane_enable() β€” start here, always

SSL_CTX_dane_enable() must be called first, to initialize the shared state that DANE support needs. then the individual connections hanging off that context can each turn on per-connection DANE support as appropriate. πŸ‘‘

DANE authentication is implemented inside X509_verify_cert(3). and if your app overrides X509_verify_cert(3) via SSL_CTX_set_cert_verify_callback(3), then you are responsible for authenticating the peer chain in whatever manner you see fit. so, like, that part is on you bestie πŸ™ˆ

πŸŽ€ SSL_CTX_dane_mtype_set() β€” the digest algorithm vibe check

SSL_CTX_dane_mtype_set() may then be called zero or more times, to adjust the supported digest algorithms. ⚠️ this must be done before any SSL handles are created for the context β€” no sliding in late, it will not work.

the args, one by one:

  • mtype β€” specifies a DANE TLSA matching type.
  • md β€” specifies the associated digest algorithm handle.
  • ord β€” specifies a strength ordinal. algorithms with a larger strength ordinal are considered more secure. these ordinals are literally how RFC7671 digest algorithm agility gets implemented. ✨

specifying a NULL digest algorithm for a matching type disables support for that matching type. and matching type Full(0)? can't be modified. can't be disabled. periodt. πŸ’€

defaults, out of the box:

  • matching type "SHA2-256(1)" is mapped to EVP_sha256() with a strength ordinal of 1 β€” (see RFC7218 for definitions of the DANE TLSA parameter acronyms, iykyk)
  • matching type "SHA2-512(2)" is mapped to EVP_sha512() with a strength ordinal of 2

πŸ¦‹ SSL_dane_enable() β€” per connection, before the handshake

SSL_dane_enable() must be called before the SSL handshake is initiated with SSL_connect(3) β€” if (and only if) you want DANE enabled for that connection. and yeah, the connection must be associated with a DANE-enabled SSL context, obviously. πŸ™„πŸ’…

  • basedomain β€” specifies the RFC7671 TLSA base domain, and it will be the primary peer reference identifier for certificate name checks. you can add more server names via SSL_add1_dnsname(3). and if no SNI hint has yet been specified via SSL_set_tlsext_host_name(3), then basedomain is used as the default SNI hint.

🌸 SSL_dane_tlsa_add() β€” load up those TLSA records

SSL_dane_tlsa_add() may then be called one or more times, to load each of the TLSA records that apply to the remote TLS peer. (and this too must be done prior to the beginning of the SSL handshake β€” the clock is ticking ⏰)

the arguments specify the fields of the TLSA record:

  • data β€” provided in binary (wire RDATA) form, not the hexadecimal ASCII presentation form, with an explicit length passed via dlen.
  • the library takes a copy of the data buffer contents, so the caller may free the original data buffer whenever it's convenient. you're welcome 🫢

return value tea β˜•

  • a return value of 0 indicates that "unusable" TLSA records (with invalid or unsupported parameters) were provided.
  • a negative return value indicates an internal error in processing the record.
The caller is expected to check the return value of each SSL_dane_tlsa_add() call and take appropriate action if none are usable or an internal error is encountered in processing some records.

and this next bit is important so please internalize it: if no TLSA records are added successfully, then DANE authentication is not enabled, and authentication will be based on any configured traditional trust-anchors. authentication success in that case does not mean that the peer was DANE-authenticated. it is simply not the same thing. 😭

πŸ‘€ SSL_get0_dane_authority() β€” who matched??

once the connection has completed successfully, SSL_get0_dane_authority() gets you more detailed info about the matched DANE trust-anchor.

the return value decoder ring:

  • negative β†’ DANE verification failed (or DANE was not enabled).
  • 0 β†’ an EE TLSA record directly matched the leaf certificate.
  • positive β†’ the depth at which a TA record matched an issuer certificate.

the complete verified chain can be retrieved via SSL_get0_verified_chain(3). πŸ“Œ and heads up: the return value is an index into this verified chain β€” not into the list of certificates sent by the peer as returned by SSL_get_peer_cert_chain(3). two different lists. don't get it twisted.

if the mcert argument is not NULL and a TLSA record matched a chain certificate, a pointer to the matching certificate is returned via mcert. that returned address is a short-term internal reference to the certificate and must not be freed by the application. if you want to retain access to it, call X509_up_ref(3) to obtain a long-term reference, and then free that via X509_free(3) once it's no longer needed. πŸ’…

now β€” if no TLSA records directly matched any elements of the certificate chain, but a DANE-TA(2) SPKI(1) Full(0) record provided the public key that signed an element of the chain, then that key is returned via the mspki argument (if it's not NULL). in this case the return value is the depth of the top-most element of the validated certificate chain. and just like with mcert, this is a short-term internal reference: EVP_PKEY_up_ref(3) and EVP_PKEY_free(3) can be used to acquire and release long-term references respectively. πŸŽ€

πŸ’œ SSL_get0_dane_tlsa() β€” gimme the fields

SSL_get0_dane_tlsa() retrieves the fields of the TLSA record that matched the peer certificate chain. the return value indicates the match depth, or the failure to match, exactly like with SSL_get0_dane_authority().

when the return value is nonnegative, the storage pointed to by the usage, selector, mtype and data parameters is updated to the corresponding TLSA record fields.

  • data is in binary wire form, and is therefore not NUL-terminated β€” its length comes back via the dlen parameter.
  • if any of those parameters is NULL, the corresponding field is simply not returned. no stress.
  • data is set to a short-term internal-copy of the associated data field and must not be freed by the application. if you need long-term access to this field, copy the content yourself bestie. πŸ“

✨ the flags crew β€” SSL_CTX_dane_set_flags() and friends

SSL_CTX_dane_set_flags() and SSL_dane_set_flags() can be used to enable optional DANE verification features. SSL_CTX_dane_clear_flags() and SSL_dane_clear_flags() can be used to disable those same features. the flags argument is a bit-mask of the features to enable or disable.

the flags set for an SSL_CTX context are copied to each SSL handle associated with that context at the time the handle is created. subsequent changes in the context's flags have no effect on the flags set for the handle. 🫠

at present, the only available option is:

  • DANE_FLAG_NO_DANE_EE_NAMECHECKS β€” disables server name checks when authenticating via DANE-EE(3) TLSA records.
⚠️ for some applications, primarily web browsers, it is not safe to disable name checks, because of "unknown key share" attacks β€” in which a malicious server can convince a client that a connection to a victim server is instead a secure connection to the malicious server. the malicious server may then be able to violate cross-origin scripting restrictions. πŸ’€

so, despite the text of RFC7671, name checks are by default enabled for DANE-EE(3) TLSA records, and can be disabled in applications where it's actually safe to do so. in particular, SMTP and XMPP clients should set this option β€” because SRV and MX records already make it possible for a remote domain to redirect client connections to any server of its choice, and in any case SMTP and XMPP clients do not execute scripts downloaded from remote servers. 🌈


πŸŽ€ RETURN VALUES πŸŽ€

SSL_CTX_dane_enable(), SSL_CTX_dane_mtype_set(), SSL_dane_enable() and SSL_dane_tlsa_add() return a positive value on success. πŸ₯³

  • negative return values indicate resource problems (out of memory, etc.) in the SSL library.
  • a return value of 0 indicates incorrect usage or invalid input β€” such as an unsupported TLSA record certificate usage, selector or matching type.
  • invalid input also includes malformed data: either a digest length that does not match the digest algorithm, or a Full(0) (binary ASN.1 DER form) certificate, or a public key that fails to parse. πŸ™ˆ

SSL_get0_dane_authority() and SSL_get0_dane_tlsa() return a negative value when DANE authentication failed or was not enabled. a nonnegative value indicates the chain depth at which the TLSA record matched a chain certificate β€” or the depth of the top-most certificate, when the TLSA record is a full public key that is its signer.

SSL_CTX_dane_set_flags(), SSL_CTX_dane_clear_flags(), SSL_dane_set_flags() and SSL_dane_clear_flags() return the flags in effect before they were called. πŸ’…

✨ EXAMPLES ✨

okay bestie, real talk, here's the vibe πŸ’– Say "smtp.example.com" is the MX host for the domain "example.com", and it's got DNSSEC-validated TLSA records. The calls below are gonna do DANE authentication AND arrange to match either the MX hostname or the destination domain name in the SMTP server certificate. Wildcards are supported btw, but they have to match the entire label β€” no partial matching, periodt. The actual name that got matched in the certificate (which might be a wildcard, ngl) gets retrieved, and your app has to copy it if it wants to keep it around past the lifetime of the SSL connection. πŸ¦‹

SSL_CTX *ctx;
SSL *ssl;
int (*verify_cb)(int ok, X509_STORE_CTX *sctx) = NULL;
int num_usable = 0;
const char *nexthop_domain = "example.com";
const char *dane_tlsa_domain = "smtp.example.com";
uint8_t usage, selector, mtype;

if ((ctx = SSL_CTX_new(TLS_client_method())) == NULL)
    /* error */
if (SSL_CTX_dane_enable(ctx) <= 0)
    /* error */
if ((ssl = SSL_new(ctx)) == NULL)
    /* error */
if (SSL_dane_enable(ssl, dane_tlsa_domain) <= 0)
    /* error */

now the spicy part πŸ™ˆ For lots of applications it's genuinely safe to skip DANE-EE(3) namechecks, but like… do not disable the checks unless "unknown key share" attacks pose no risk to your app. That's the vibe check. πŸ”₯

/*
 * For many applications it is safe to skip DANE-EE(3) namechecks.  Do not
 * disable the checks unless "unknown key share" attacks pose no risk for
 * your application.
 */
SSL_dane_set_flags(ssl, DANE_FLAG_NO_DANE_EE_NAMECHECKS);

if (!SSL_add1_dnsname(ssl, nexthop_domain))
    /* error */
SSL_set_hostflags(ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);

then we loop over every TLSA record and sort the usable ones from the ones that are just… not it πŸ’…

for (... each TLSA record ...) {
    unsigned char *data;
    size_t len;
    int ret;

    /* set usage, selector, mtype, data, len */

    /*
     * Opportunistic DANE TLS clients support only DANE-TA(2) or DANE-EE(3).
     * They treat all other certificate usages, and in particular PKIX-TA(0)
     * and PKIX-EE(1), as unusable.
     */
    switch (usage) {
    default:
    case 0:     /* PKIX-TA(0) */
    case 1:     /* PKIX-EE(1) */
        continue;
    case 2:     /* DANE-TA(2) */
    case 3:     /* DANE-EE(3) */
        break;
    }

    ret = SSL_dane_tlsa_add(ssl, usage, selector, mtype, data, len);
    /* free data as appropriate */

    if (ret < 0)
        /* handle SSL library internal error */
    else if (ret == 0)
        /* handle unusable TLSA record */
    else
        ++num_usable;
}

okay so at this point the verification mode is still the default SSL_VERIFY_NONE. Iconic of it. Opportunistic DANE clients just use unauthenticated TLS when every single TLSA record is unusable, so they continue the handshake even if authentication fails. Lowkey unhinged, highkey correct. 😌

/*
 * At this point, the verification mode is still the default SSL_VERIFY_NONE.
 * Opportunistic DANE clients use unauthenticated TLS when all TLSA records
 * are unusable, so continue the handshake even if authentication fails.
 */
if (num_usable == 0) {
    /* Log all records unusable? */

    /* Optionally set verify_cb to a suitable non-NULL callback. */
    SSL_set_verify(ssl, SSL_VERIFY_NONE, verify_cb);
} else {
    /* At least one usable record.  We expect to verify the peer */

    /* Optionally set verify_cb to a suitable non-NULL callback. */

    /*
     * Below we elect to fail the handshake when peer verification fails.
     * Alternatively, use the permissive SSL_VERIFY_NONE verification mode,
     * complete the handshake, check the verification status, and if not
     * verified disconnect gracefully at the application layer, especially if
     * application protocol supports informing the server that authentication
     * failed.
     */
    SSL_set_verify(ssl, SSL_VERIFY_PEER, verify_cb);
}

then load any saved session for resumption β€” and bestie, make sure the previous session applied the same security and authentication requirements you'd expect from a fresh connection, or it's a whole mess 🫢

/*
 * Load any saved session for resumption, making sure that the previous
 * session applied the same security and authentication requirements that
 * would be expected of a fresh connection.
 */

/* Perform SSL_connect() handshake and handle errors here */

and finally, the moment of truth β€” what actually happened? πŸ‘‘

if (SSL_session_reused(ssl)) {
    if (SSL_get_verify_result(ssl) == X509_V_OK) {
        /*
         * Resumed session was originally verified, this connection is
         * authenticated.
         */
    } else {
        /*
         * Resumed session was not originally verified, this connection is not
         * authenticated.
         */
    }
} else if (SSL_get_verify_result(ssl) == X509_V_OK) {
    const char *peername = SSL_get0_peername(ssl);
    EVP_PKEY *mspki = NULL;

    int depth = SSL_get0_dane_authority(ssl, NULL, &mspki);
    if (depth >= 0) {
        (void) SSL_get0_dane_tlsa(ssl, &usage, &selector, &mtype, NULL, NULL);
        printf("DANE TLSA %d %d %d ", usage, selector, mtype);
        if (SSL_get0_peer_rpk(ssl) == NULL)
            printf("%s certificate at depth %d\n",
                   (mspki != NULL) ? "signed the peer" :
                   mdpth ? "matched the TA" : "matched the EE", mdpth);
        else
            printf(bio, "matched the peer raw public key\n");
    }
    if (peername != NULL) {
        /* Name checks were in scope and matched the peername */
        printf("Verified peername: %s\n", peername);
    }
} else {
    /*
     * Not authenticated, presumably all TLSA rrs unusable, but possibly a
     * callback suppressed connection termination despite the presence of
     * usable TLSA RRs none of which matched.  Do whatever is appropriate for
     * fresh unauthenticated connections.
     */
}

πŸ“ NOTES πŸ“

so the expectation is that the majority of clients doing DANE TLS are doing "opportunistic DANE TLS" in the RFC7672 and RFC7435 sense. Translation: they use DANE authentication when there are DNSSEC-validated TLSA records published for a given peer, and otherwise they just fall back to unauthenticated TLS or even literally cleartext. πŸ’€ fr fr.

Those kinds of apps should generally treat any TLSA records the peer publishes with usages PKIX-TA(0) and PKIX-EE(1) as "unusable", and should not include them among the TLSA records they use to authenticate peer connections. Also worth knowing: some TLSA records with supported usages can still be "unusable" if the parameters are invalid or unsupported. Not everything that looks valid IS valid, iykyk. 🧚

When a peer has TLSA records but none are "usable", an opportunistic app must avoid cleartext, but it can't authenticate the peer either β€” so it should generally just proceed with an unauthenticated connection. Opportunistic apps need to note the return value of every single call to SSL_dane_tlsa_add(), and if they all return 0 (because of invalid or unsupported parameters), disable peer authentication by calling SSL_set_verify(3) with mode equal to SSL_VERIFY_NONE. That's the move. πŸ’…

okay real talk though β€” watch those return values, girlie. All zeros means the vibe is unauthenticated, and you gotta handle that on purpose, not by accident. πŸ™ˆ

πŸ‘€ SEE ALSO πŸ‘€


🌈 HISTORY 🌈

these functions were added in OpenSSL 1.1.0. so they've been serving looks for a minute. ✨


Copyright 2016-2026 The OpenSSL Project Authors. All Rights Reserved.

Licensed under the Apache License 2.0 (the "License"). You may not use this file except in compliance with the License. You can obtain a copy in the file LICENSE in the source distribution or at https://www.openssl.org/source/license.html.


okay that's the whole page bestie, we ate and left no crumbs πŸŽ€ this was SSL_CTX_DANE_ENABLE(3SSL) from OpenSSL 4.0.3, dated 2026-09-29 β€” stay safe out there in the handshake, mwah πŸ’œπŸ’…

πŸ“¦ where this page comes from