]> git.ozlabs.org Git - ppp.git/blobdiff - pppd/tls.c
Merge pull request #307 from enaess/ppp-peap
[ppp.git] / pppd / tls.c
diff --git a/pppd/tls.c b/pppd/tls.c
new file mode 100644 (file)
index 0000000..28c2e57
--- /dev/null
@@ -0,0 +1,447 @@
+/*
+ * Copyright (c) 2021 Eivind Næss. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The name(s) of the authors of this software must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission.
+ *
+ * THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO
+ * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <string.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/x509v3.h>
+
+#include "pppd.h"
+#include "tls.h"
+
+/**
+ * Structure used in verifying the peer certificate
+ */
+struct tls_info
+{
+    char *peer_name;
+    X509 *peer_cert;
+    bool client;
+};
+
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+
+/*
+ *  OpenSSL 1.1+ introduced a generic TLS_method()
+ *  For older releases we substitute the appropriate method
+ */
+#define TLS_method SSLv23_method
+
+#ifndef SSL_CTX_set_max_proto_version
+/** Mimics SSL_CTX_set_max_proto_version for OpenSSL < 1.1 */
+static inline int SSL_CTX_set_max_proto_version(SSL_CTX *ctx, long tls_ver_max)
+{
+    long sslopt = 0;
+
+    if (tls_ver_max < TLS1_VERSION)
+    {
+        sslopt |= SSL_OP_NO_TLSv1;
+    }
+#ifdef SSL_OP_NO_TLSv1_1
+    if (tls_ver_max < TLS1_1_VERSION)
+    {
+        sslopt |= SSL_OP_NO_TLSv1_1;
+    }
+#endif
+#ifdef SSL_OP_NO_TLSv1_2
+    if (tls_ver_max < TLS1_2_VERSION)
+    {
+        sslopt |= SSL_OP_NO_TLSv1_2;
+    }
+#endif
+    SSL_CTX_set_options(ctx, sslopt);
+
+    return 1;
+}
+#endif /* SSL_CTX_set_max_proto_version */
+
+#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
+
+
+/*
+ * Verify a certificate. Most of the work (signatures and issuer attributes checking)
+ * is done by ssl; we check the CN in the peer certificate against the peer name.
+ */
+static int tls_verify_callback(int ok, X509_STORE_CTX *ctx)
+{
+    char subject[256];
+    char cn_str[256];
+    X509 *peer_cert;
+    int err, depth;
+    SSL *ssl;
+    struct tls_info *inf;
+    char *ptr1 = NULL, *ptr2 = NULL;
+
+    peer_cert = X509_STORE_CTX_get_current_cert(ctx);
+    err = X509_STORE_CTX_get_error(ctx);
+    depth = X509_STORE_CTX_get_error_depth(ctx);
+
+    dbglog("certificate verify depth: %d", depth);
+
+    if (auth_required && !ok) {
+        X509_NAME_oneline(X509_get_subject_name(peer_cert),
+                  subject, 256);
+
+        X509_NAME_get_text_by_NID(X509_get_subject_name(peer_cert),
+                      NID_commonName, cn_str, 256);
+
+        dbglog("Certificate verification error:\n depth: %d CN: %s"
+               "\n err: %d (%s)\n", depth, cn_str, err,
+               X509_verify_cert_error_string(err));
+
+        return 0;
+    }
+
+    ssl = X509_STORE_CTX_get_ex_data(ctx,
+                       SSL_get_ex_data_X509_STORE_CTX_idx());
+
+    inf = (struct tls_info*) SSL_get_ex_data(ssl, 0);
+    if (inf == NULL) {
+        error("Error: SSL_get_ex_data returned NULL");
+        return 0;
+    }
+
+    tls_log_sslerr();
+
+    if (!depth) 
+    {
+        /* Verify certificate based on certificate type and extended key usage */
+        if (tls_verify_key_usage) {
+            int purpose = inf->client ? X509_PURPOSE_SSL_SERVER : X509_PURPOSE_SSL_CLIENT ;
+            if (X509_check_purpose(peer_cert, purpose, 0) == 0) {
+                error("Certificate verification error: nsCertType mismatch");
+                return 0;
+            }
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+            int flags = inf->client ? XKU_SSL_SERVER : XKU_SSL_CLIENT;
+            if (!(X509_get_extended_key_usage(peer_cert) & flags)) {
+                error("Certificate verification error: invalid extended key usage");
+                return 0;
+            }
+#endif
+            info("Certificate key usage: OK");
+        }
+
+        /*
+         * If acting as client and the name of the server wasn't specified
+         * explicitely, we can't verify the server authenticity 
+         */
+        if (!tls_verify_method)
+            tls_verify_method = TLS_VERIFY_NONE;
+
+        if (!inf->peer_name || !strcmp(TLS_VERIFY_NONE, tls_verify_method)) {
+            warn("Certificate verication disabled or no peer name was specified");
+            return ok;
+        }
+
+        /* This is the peer certificate */
+        X509_NAME_oneline(X509_get_subject_name(peer_cert),
+                  subject, 256);
+
+        X509_NAME_get_text_by_NID(X509_get_subject_name(peer_cert),
+                      NID_commonName, cn_str, 256);
+
+        /* Verify based on subject name */
+        ptr1 = inf->peer_name;
+        if (!strcmp(TLS_VERIFY_SUBJECT, tls_verify_method)) {
+            ptr2 = subject;
+        }
+
+        /* Verify based on common name (default) */
+        if (strlen(tls_verify_method) == 0 ||
+            !strcmp(TLS_VERIFY_NAME, tls_verify_method)) {
+            ptr2 = cn_str;
+        }
+
+        /* Match the suffix of common name */
+        if (!strcmp(TLS_VERIFY_SUFFIX, tls_verify_method)) {
+            int len = strlen(ptr1);
+            int off = strlen(cn_str) - len;
+            ptr2 = cn_str;
+            if (off > 0) {
+                ptr2 = cn_str + off;
+            }
+        }
+
+        if (strcmp(ptr1, ptr2)) {
+            error("Certificate verification error: CN (%s) != %s", ptr1, ptr2);
+            return 0;
+        }
+
+        if (inf->peer_cert) { 
+            if (X509_cmp(inf->peer_cert, peer_cert) != 0) {
+                error("Peer certificate doesn't match stored certificate");
+                return 0;
+            }
+        }
+
+        info("Certificate CN: %s, peer name %s", cn_str, inf->peer_name);
+    }
+
+    return ok;
+}
+
+int tls_init()
+{
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+    SSL_library_init();
+    SSL_load_error_strings();
+#endif
+    return 0;
+}
+
+int tls_set_verify(SSL_CTX *ctx, int depth) 
+{
+    SSL_CTX_set_verify_depth(ctx, depth);
+    SSL_CTX_set_verify(ctx,
+               SSL_VERIFY_PEER |
+               SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+               &tls_verify_callback);
+    return 0;
+}
+
+int tls_set_verify_info(SSL *ssl, const char *peer_name, const char *peer_cert, 
+        bool client, struct tls_info **out)
+{
+    if (out != NULL) {
+        struct tls_info *tmp = calloc(sizeof(struct tls_info), 1);
+        if (!tmp) {
+            fatal("Allocation error");
+        }
+
+        tmp->client = client;
+        if (peer_name) {
+            tmp->peer_name = strdup(peer_name);
+        }
+
+        if (peer_cert && strlen(peer_cert) > 0) {
+            FILE *fp = fopen(peer_cert, "r");
+            if (fp) {
+                tmp->peer_cert = PEM_read_X509(fp, NULL, NULL, NULL);
+                fclose(fp);
+            }
+
+            if (!tmp->peer_cert) {
+                error("EAP-TLS: Error loading client certificate from file %s",
+                     peer_cert);
+                tls_free_verify_info(&tmp);
+                return -1;
+            }
+        }
+
+        SSL_set_ex_data(ssl, 0, tmp);
+        *out = tmp;
+        return 0;
+    }
+
+    return -1;
+}
+
+void tls_free_verify_info(struct tls_info **in) {
+    if (in && *in) {
+        struct tls_info *tmp = *in;
+        if (tmp->peer_name) {
+            free(tmp->peer_name);
+        }
+        if (tmp->peer_cert) {
+            X509_free(tmp->peer_cert);
+        }
+        free(tmp);
+        *in = NULL;
+    }
+}
+
+const SSL_METHOD* tls_method() {
+    return TLS_method();
+}
+
+int tls_set_version(SSL_CTX *ctx, const char *max_version)
+{
+#if defined(TLS1_2_VERSION)
+    long tls_version = TLS1_2_VERSION; 
+#elif defined(TLS1_1_VERSION)
+    long tls_version = TLS1_1_VERSION; 
+#else
+    long tls_version = TLS1_VERSION; 
+#endif
+
+    /* As EAP-TLS+TLSv1.3 is highly experimental we offer the user a chance to override */
+    if (max_version) {
+        if (strncmp(max_version, "1.0", 3) == 0) {
+            tls_version = TLS1_VERSION;
+        }
+        else if (strncmp(max_version, "1.1", 3) == 0) {
+            tls_version = TLS1_1_VERSION;
+        }
+        else if (strncmp(max_version, "1.2", 3) == 0) {
+#ifdef TLS1_2_VERSION
+            tls_version = TLS1_2_VERSION;
+#else
+            warn("TLSv1.2 not available. Defaulting to TLSv1.1");
+            tls_version = TLS_1_1_VERSION;
+#endif
+        }
+        else if (strncmp(max_version, "1.3", 3) == 0) {
+#ifdef TLS1_3_VERSION
+            tls_version = TLS1_3_VERSION;
+#else
+            warn("TLSv1.3 not available.");
+#endif
+        }
+    }
+
+    dbglog("Setting max protocol version to 0x%X", tls_version);
+    if (!SSL_CTX_set_max_proto_version(ctx, tls_version)) {
+        error("Could not set max protocol version");
+        return -1;
+    }
+
+    return 0;
+}
+
+int tls_set_opts(SSL_CTX *ctx) {
+    
+    /* Explicitly set the NO_TICKETS flag to support Win7/Win8 clients */
+    SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3
+#ifdef SSL_OP_NO_TICKET
+    | SSL_OP_NO_TICKET
+#endif
+    | SSL_OP_NO_COMPRESSION
+    );
+
+    /* OpenSSL 1.1.1+ does not include RC4 ciphers by default.
+     * This causes totally obsolete WinXP clients to fail. If you really
+     * need ppp+EAP-TLS+openssl 1.1.1+WinXP then enable RC4 cipers and
+     * make sure that you use an OpenSSL that supports them
+
+    SSL_CTX_set_cipher_list(ctx, "RC4");
+    */
+    return 0;
+}
+
+int tls_set_crl(SSL_CTX *ctx, const char *crl_dir, const char *crl_file) 
+{
+    X509_STORE  *certstore = NULL;
+    X509_LOOKUP *lookup = NULL;
+    FILE *fp = NULL;
+    int status = -1;
+
+    if (crl_dir) {
+        if (!(certstore = SSL_CTX_get_cert_store(ctx))) {
+            error("Failed to get certificate store");
+            goto done;
+        }
+
+        if (!(lookup =
+             X509_STORE_add_lookup(certstore, X509_LOOKUP_hash_dir()))) {
+            error("Store lookup for CRL failed");
+            goto done;
+        }
+
+        X509_LOOKUP_add_dir(lookup, crl_dir, X509_FILETYPE_PEM);
+        X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK);
+    }
+
+    if (crl_file) {
+        X509_CRL *crl = NULL;
+
+        fp = fopen(crl_file, "r");
+        if (!fp) {
+            error("Cannot open CRL file '%s'", crl_file);
+            goto done;
+        }
+
+        crl = PEM_read_X509_CRL(fp, NULL, NULL, NULL);
+        if (!crl) {
+            error("Cannot read CRL file '%s'", crl_file);
+            goto done;
+        }
+
+        if (!(certstore = SSL_CTX_get_cert_store(ctx))) {
+            error("Failed to get certificate store");
+            goto done;
+        }
+        if (!X509_STORE_add_crl(certstore, crl)) {
+            error("Cannot add CRL to certificate store");
+            goto done;
+        }
+        X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK);
+    }
+
+    status = 0;
+
+done: 
+
+    if (fp != NULL) {
+        fclose(fp);
+    }
+
+    return status;
+}
+
+int tls_set_ca(SSL_CTX *ctx, const char *ca_dir, const char *ca_file) 
+{
+    if (ca_file && strlen(ca_file) == 0) {
+        ca_file = NULL;
+    }
+
+    if (ca_dir && strlen(ca_dir) == 0) {
+        ca_dir = NULL;
+    }
+
+    if (!SSL_CTX_load_verify_locations(ctx, ca_file, ca_dir)) {
+
+        error("Cannot load verify locations");
+        if (ca_file) {
+            dbglog("CA certificate file = [%s]", ca_file);
+        }
+
+        if (ca_dir) {
+            dbglog("CA certificate path = [%s]", ca_dir);
+        }
+
+        return -1;
+    }
+
+    return 0;
+}
+
+void tls_log_sslerr( void )
+{
+    unsigned long ssl_err = ERR_get_error();
+
+    if (ssl_err != 0)
+        dbglog("EAP-TLS SSL error stack:");
+    while (ssl_err != 0) {
+        dbglog( ERR_error_string( ssl_err, NULL ) );
+        ssl_err = ERR_get_error();
+    }
+}
+