openssl: Continue on UNABLE_TO_VERIFY_LEAF_SIGNATURE

We like to request that it is possible (maybe by an explicit option) to continue certificate verification even if UNABLE_TO_VERIFY_LEAF_SIGNATURE is thrown.

We are implementing the official eID-Client of the German identity card. The whole system is defined by a technical guideline of the Federal Office for Information Security. [1]

The identity card uses NFC to establish a connection between server and the card itself. These connections are also secured by TLS. It is not possible to use root-CAs from system truststore as the German eID infrastructure uses it’s own trust anchor. This trust anchor is a CVC (Card Verifiable Certificate) which is verified by the card itself. The CVC contains hash values of the allowed TLS certificates.

When we establish a secured connection via TLS we need to check certain things like certificate validity. But as we don’t have a root CA we need to ignore “UNABLE_TO_VERIFY_LEAF_SIGNATURE” error. Even though the content of the certificate MIGHT be compromised we MUST do plausibility checks (like validity) because we can approve it LATER with our received CVC. However, openssl stops the whole validation. This breaks our workflow as it would allow that an expired certificate is not fatal.

We checked mbedtls and gnutls. Those implementations throw a similar error but don’t stop the whole verification.

It is not possible to enforce all eID service providers to send the WHOLE certificate chain including the root-CA. See RFC5246 section-7.4.2.

   certificate_list
      This is a sequence (chain) of certificates.  The sender's
      certificate MUST come first in the list.  Each following
      certificate MUST directly certify the one preceding it.  Because
      certificate validation requires that root keys be distributed
      independently, the self-signed certificate that specifies the root
      certificate authority MAY be omitted from the chain, under the
      assumption that the remote end must already possess it in order to
      validate it in any case.

[1] https://www.bsi.bund.de/SharedDocs/Downloads/DE/BSI/Publikationen/TechnischeRichtlinien/TR03124/TR-03124-1.pdf?__blob=publicationFile&v=2

[2] https://tools.ietf.org/html/rfc5246#section-7.4.2

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 18 (17 by maintainers)

Commits related to this issue

Most upvoted comments

In the callback you decide what OpenSSL should do: ignore the error and continue or abort. It seems that you’re telling it to ignore the error.

I’m not sure how the test works, but X509_verify_cert() should return the same if the certificate has not expired.

Fixed for me. 😃 Thank you very much!

I just grepped for all verify_cb_cert(), and am noticing that we do this “direct return” in more places. I believe that the reasoning is that these are spots where the callback should take over the verification completely from the point in the chain, and simply return yay or nay, which then gets propagated back.

$ git grep -n -C1 verify_cb_cert
crypto/x509/x509_vfy.c-169- */
crypto/x509/x509_vfy.c:170:static int verify_cb_cert(X509_STORE_CTX *ctx, X509 *x, int depth, int err)
crypto/x509/x509_vfy.c-171-{
--
crypto/x509/x509_vfy.c-207-        if (i > 0 && !check_key_level(ctx, cert) &&
crypto/x509/x509_vfy.c:208:            verify_cb_cert(ctx, cert, i, X509_V_ERR_CA_KEY_TOO_SMALL) == 0)
crypto/x509/x509_vfy.c-209-            return 0;
--
crypto/x509/x509_vfy.c-214-        if (i < num - 1 && !check_sig_level(ctx, cert) &&
crypto/x509/x509_vfy.c:215:            verify_cb_cert(ctx, cert, i, X509_V_ERR_CA_MD_TOO_WEAK) == 0)
crypto/x509/x509_vfy.c-216-            return 0;
--
crypto/x509/x509_vfy.c-240-    if (err != X509_V_OK) {
crypto/x509/x509_vfy.c:241:        if ((ok = verify_cb_cert(ctx, NULL, ctx->error_depth, err)) == 0)
crypto/x509/x509_vfy.c-242-            return ok;
--
crypto/x509/x509_vfy.c-295-    if (!check_key_level(ctx, ctx->cert) &&
crypto/x509/x509_vfy.c:296:        !verify_cb_cert(ctx, ctx->cert, 0, X509_V_ERR_EE_KEY_TOO_SMALL))
crypto/x509/x509_vfy.c-297-        return 0;
--
crypto/x509/x509_vfy.c-441-
crypto/x509/x509_vfy.c:442:    return verify_cb_cert(ctx, x, depth, X509_V_ERR_INVALID_PURPOSE);
crypto/x509/x509_vfy.c-443-}
--
crypto/x509/x509_vfy.c-484-            && (x->ex_flags & EXFLAG_CRITICAL)) {
crypto/x509/x509_vfy.c:485:            if (!verify_cb_cert(ctx, x, i,
crypto/x509/x509_vfy.c-486-                                X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION))
--
crypto/x509/x509_vfy.c-489-        if (!allow_proxy_certs && (x->ex_flags & EXFLAG_PROXY)) {
crypto/x509/x509_vfy.c:490:            if (!verify_cb_cert(ctx, x, i,
crypto/x509/x509_vfy.c-491-                                X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED))
--
crypto/x509/x509_vfy.c-527-        }
crypto/x509/x509_vfy.c:528:        if (ret == 0 && !verify_cb_cert(ctx, x, i, X509_V_OK))
crypto/x509/x509_vfy.c-529-            return 0;
--
crypto/x509/x509_vfy.c-535-            && (plen > (x->ex_pathlen + proxy_path_length))) {
crypto/x509/x509_vfy.c:536:            if (!verify_cb_cert(ctx, x, i, X509_V_ERR_PATH_LENGTH_EXCEEDED))
crypto/x509/x509_vfy.c-537-                return 0;
--
crypto/x509/x509_vfy.c-560-                if (proxy_path_length > x->ex_pcpathlen) {
crypto/x509/x509_vfy.c:561:                    if (!verify_cb_cert(ctx, x, i,
crypto/x509/x509_vfy.c-562-                                        X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED))
--
crypto/x509/x509_vfy.c-676-            if (err != X509_V_OK
crypto/x509/x509_vfy.c:677:                && !verify_cb_cert(ctx, x, i, err))
crypto/x509/x509_vfy.c-678-                return 0;
--
crypto/x509/x509_vfy.c-707-                default:
crypto/x509/x509_vfy.c:708:                    if (!verify_cb_cert(ctx, x, i, rv))
crypto/x509/x509_vfy.c-709-                        return 0;
--
crypto/x509/x509_vfy.c-719-{
crypto/x509/x509_vfy.c:720:    return verify_cb_cert(ctx, ctx->cert, 0, errcode);
crypto/x509/x509_vfy.c-721-}
--
crypto/x509/x509_vfy.c-841- rejected:
crypto/x509/x509_vfy.c:842:    if (!verify_cb_cert(ctx, x, i, X509_V_ERR_CERT_REJECTED))
crypto/x509/x509_vfy.c-843-        return X509_TRUST_REJECTED;
--
crypto/x509/x509_vfy.c-1642-                continue;
crypto/x509/x509_vfy.c:1643:            if (!verify_cb_cert(ctx, x, i,
crypto/x509/x509_vfy.c-1644-                                X509_V_ERR_INVALID_POLICY_EXTENSION))
--
crypto/x509/x509_vfy.c-1695-        return 0;
crypto/x509/x509_vfy.c:1696:    if (i == 0 && !verify_cb_cert(ctx, x, depth,
crypto/x509/x509_vfy.c-1697-                                  X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD))
crypto/x509/x509_vfy.c-1698-        return 0;
crypto/x509/x509_vfy.c:1699:    if (i > 0 && !verify_cb_cert(ctx, x, depth, X509_V_ERR_CERT_NOT_YET_VALID))
crypto/x509/x509_vfy.c-1700-        return 0;
--
crypto/x509/x509_vfy.c-1704-        return 0;
crypto/x509/x509_vfy.c:1705:    if (i == 0 && !verify_cb_cert(ctx, x, depth,
crypto/x509/x509_vfy.c-1706-                                  X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD))
crypto/x509/x509_vfy.c-1707-        return 0;
crypto/x509/x509_vfy.c:1708:    if (i < 0 && !verify_cb_cert(ctx, x, depth, X509_V_ERR_CERT_HAS_EXPIRED))
crypto/x509/x509_vfy.c-1709-        return 0;
--
crypto/x509/x509_vfy.c-1738-        if (n <= 0)
crypto/x509/x509_vfy.c:1739:            return verify_cb_cert(ctx, xi, 0,
crypto/x509/x509_vfy.c-1740-                                  X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE);
--
crypto/x509/x509_vfy.c-1785-
crypto/x509/x509_vfy.c:1786:            if (ret != X509_V_OK && !verify_cb_cert(ctx, xi, issuer_depth, ret))
crypto/x509/x509_vfy.c-1787-                return 0;
--
crypto/x509/x509_vfy.c-1789-                ret = X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY;
crypto/x509/x509_vfy.c:1790:                if (!verify_cb_cert(ctx, xi, issuer_depth, ret))
crypto/x509/x509_vfy.c-1791-                    return 0;
--
crypto/x509/x509_vfy.c-1793-                ret = X509_V_ERR_CERT_SIGNATURE_FAILURE;
crypto/x509/x509_vfy.c:1794:                if (!verify_cb_cert(ctx, xs, n, ret))
crypto/x509/x509_vfy.c-1795-                    return 0;
--
crypto/x509/x509_vfy.c-2878-        return 1;
crypto/x509/x509_vfy.c:2879:    return verify_cb_cert(ctx, cert, 0, err);
crypto/x509/x509_vfy.c-2880-}
--
crypto/x509/x509_vfy.c-2933-            return 0;
crypto/x509/x509_vfy.c:2934:        return verify_cb_cert(ctx, cert, 0, X509_V_ERR_DANE_NO_MATCH);
crypto/x509/x509_vfy.c-2935-    }
--
crypto/x509/x509_vfy.c-3322-        if (num > depth)
crypto/x509/x509_vfy.c:3323:            return verify_cb_cert(ctx, NULL, num-1,
crypto/x509/x509_vfy.c-3324-                                  X509_V_ERR_CERT_CHAIN_TOO_LONG);
--
crypto/x509/x509_vfy.c-3326-            (!DANETLS_HAS_PKIX(dane) || dane->pdpth >= 0))
crypto/x509/x509_vfy.c:3327:            return verify_cb_cert(ctx, NULL, num-1, X509_V_ERR_DANE_NO_MATCH);
crypto/x509/x509_vfy.c-3328-        if (self_signed && sk_X509_num(ctx->chain) == 1)
crypto/x509/x509_vfy.c:3329:            return verify_cb_cert(ctx, NULL, num-1,
crypto/x509/x509_vfy.c-3330-                                  X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT);
crypto/x509/x509_vfy.c-3331-        if (self_signed)
crypto/x509/x509_vfy.c:3332:            return verify_cb_cert(ctx, NULL, num-1,
crypto/x509/x509_vfy.c-3333-                                  X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN);
crypto/x509/x509_vfy.c-3334-        if (ctx->num_untrusted < num)
crypto/x509/x509_vfy.c:3335:            return verify_cb_cert(ctx, NULL, num-1,
crypto/x509/x509_vfy.c-3336-                                  X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT);
crypto/x509/x509_vfy.c:3337:        return verify_cb_cert(ctx, NULL, num-1,
crypto/x509/x509_vfy.c-3338-                              X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY);

I can’t tell you why we do it differently in different spots, and haven’t the time right now to dig further for the moment. It’s possible that there is no other way in some cases.