From 9997fa8f3ebae1baac5a733525b46717541f1aae Mon Sep 17 00:00:00 2001 From: leetthewire Date: Wed, 4 Aug 2021 10:08:00 -0700 Subject: [PATCH] updated linux build --- client/configurators/openvpn_configurator.cpp | 13 +- client/protocols/openvpnprotocol.cpp | 2 + client/protocols/shadowsocksvpnprotocol.cpp | 6 + client/protocols/wireguardprotocol.cpp | 2 + client/utils.cpp | 8 + client/utils.h | 1 + deploy/data/linux/client/AmneziaVPN.desktop | 8 + deploy/data/linux/client/bin/openvpn/easyrsa | 2579 +++++++++++++++++ .../client/bin/openvpn/openssl-easyrsa.cnf | 138 + deploy/data/linux/client/bin/qt.conf | 7 + .../linux/client/bin/update-resolv-conf.sh | 71 + .../applications/AmneziaVPN_build.desktop | 8 + .../client/share/icons/AmneziaVPN_Logo.png | Bin 0 -> 56043 bytes deploy/data/linux/post_install.sh | 58 + deploy/data/linux/post_uninstall.sh | 74 + deploy/data/linux/service/AmneziaVPN.service | 13 + .../applications/AmneziaVPN_build.desktop | 7 + deploy/installer/config/controlscript.js | 7 + deploy/installer/config/linux.xml | 27 + .../meta/componentscript.js | 11 +- service/server/router.cpp | 10 + service/server/router_linux.cpp | 166 ++ service/server/router_linux.h | 38 + service/server/server.pro | 10 +- 24 files changed, 3257 insertions(+), 7 deletions(-) create mode 100644 deploy/data/linux/client/AmneziaVPN.desktop create mode 100644 deploy/data/linux/client/bin/openvpn/easyrsa create mode 100644 deploy/data/linux/client/bin/openvpn/openssl-easyrsa.cnf create mode 100644 deploy/data/linux/client/bin/qt.conf create mode 100644 deploy/data/linux/client/bin/update-resolv-conf.sh create mode 100644 deploy/data/linux/client/share/applications/AmneziaVPN_build.desktop create mode 100644 deploy/data/linux/client/share/icons/AmneziaVPN_Logo.png create mode 100644 deploy/data/linux/post_install.sh create mode 100644 deploy/data/linux/post_uninstall.sh create mode 100644 deploy/data/linux/service/AmneziaVPN.service create mode 100644 deploy/data/linux/service/share/applications/AmneziaVPN_build.desktop create mode 100644 deploy/installer/config/linux.xml create mode 100644 service/server/router_linux.cpp create mode 100644 service/server/router_linux.h diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp index 8bb8a360..9b9ecddb 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/configurators/openvpn_configurator.cpp @@ -19,6 +19,7 @@ QString OpenVpnConfigurator::getEasyRsaShPath() qDebug().noquote() << "EasyRsa sh path" << easyRsaShPath; return easyRsaShPath; + #else return QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/easyrsa"; #endif @@ -29,12 +30,14 @@ QProcessEnvironment OpenVpnConfigurator::prepareEnv() QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); QString pathEnvVar = env.value("PATH"); -#ifdef Q_OS_WIN +#if defined Q_OS_WIN pathEnvVar.clear(); pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;"); pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;"); -#else +#elif defined Q_OS_MAC pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS"); +#elif defined Q_OS_LINUX + pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/openvpn"); #endif env.insert("PATH", pathEnvVar); @@ -213,7 +216,7 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia config.replace("", ""); } -#ifdef Q_OS_MAC +#if defined Q_OS_MAC || defined(Q_OS_LINUX) config.replace("block-outside-dns", ""); #endif @@ -236,7 +239,7 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString config) } } -#ifdef Q_OS_MAC +#if defined Q_OS_MAC || defined(Q_OS_LINUX) config.replace("block-outside-dns", ""); QString dnsConf = QString( "\nscript-security 2\n" @@ -259,7 +262,7 @@ QString OpenVpnConfigurator::processConfigWithExportSettings(QString config) config.append("redirect-gateway def1 bypass-dhcp\n"); } -#ifdef Q_OS_MAC +#if defined Q_OS_MAC || defined(Q_OS_LINUX) config.replace("block-outside-dns", ""); #endif diff --git a/client/protocols/openvpnprotocol.cpp b/client/protocols/openvpnprotocol.cpp index 5371c566..a756d86d 100644 --- a/client/protocols/openvpnprotocol.cpp +++ b/client/protocols/openvpnprotocol.cpp @@ -142,6 +142,8 @@ QString OpenVpnProtocol::openVpnExecPath() const { #ifdef Q_OS_WIN return Utils::executable("openvpn/openvpn", true); +#elif defined Q_OS_LINUX + return Utils::usrExecutable("openvpn"); #else return Utils::executable("/openvpn", true); #endif diff --git a/client/protocols/shadowsocksvpnprotocol.cpp b/client/protocols/shadowsocksvpnprotocol.cpp index ae110026..3d2c2be1 100644 --- a/client/protocols/shadowsocksvpnprotocol.cpp +++ b/client/protocols/shadowsocksvpnprotocol.cpp @@ -36,8 +36,12 @@ ErrorCode ShadowSocksVpnProtocol::start() m_shadowSocksCfgFile.write(QJsonDocument(m_shadowSocksConfig).toJson()); m_shadowSocksCfgFile.close(); +#ifdef Q_OS_LINUX + QStringList args = QStringList() << "-c" << m_shadowSocksCfgFile.fileName(); +#else QStringList args = QStringList() << "-c" << m_shadowSocksCfgFile.fileName() << "--no-delay"; +#endif qDebug().noquote() << "ShadowSocksVpnProtocol::start()" << shadowSocksExecPath() << args.join(" "); @@ -91,6 +95,8 @@ QString ShadowSocksVpnProtocol::shadowSocksExecPath() { #ifdef Q_OS_WIN return Utils::executable(QString("ss/ss-local"), true); +#elif defined Q_OS_LINUX + return Utils::usrExecutable(QString("ss-local")); #else return Utils::executable(QString("/ss-local"), true); #endif diff --git a/client/protocols/wireguardprotocol.cpp b/client/protocols/wireguardprotocol.cpp index 4c5b6f0f..b3c2cd04 100644 --- a/client/protocols/wireguardprotocol.cpp +++ b/client/protocols/wireguardprotocol.cpp @@ -122,6 +122,8 @@ QString WireguardProtocol::wireguardExecPath() const { #ifdef Q_OS_WIN return Utils::executable("wireguard/wireguard", true); +#elif defined Q_OS_LINUX + return Utils::usrExecutable("wg"); #else return Utils::executable("/wireguard", true); #endif diff --git a/client/utils.cpp b/client/utils.cpp index 42056e94..ed08610e 100644 --- a/client/utils.cpp +++ b/client/utils.cpp @@ -68,6 +68,14 @@ QString Utils::executable(const QString& baseName, bool absPath) return QCoreApplication::applicationDirPath() + "/" + fileName; } +QString Utils::usrExecutable(const QString& baseName) +{ + if (QFileInfo::exists("/usr/sbin/" + baseName)) + return ("/usr/sbin/" + baseName); + else + return ("/usr/bin/" + baseName); +} + bool Utils::processIsRunning(const QString& fileName) { #ifdef Q_OS_WIN diff --git a/client/utils.h b/client/utils.h index 3a0ef2ce..709a159e 100644 --- a/client/utils.h +++ b/client/utils.h @@ -14,6 +14,7 @@ public: static QString getRandomString(int len); static QString executable(const QString& baseName, bool absPath); + static QString usrExecutable(const QString& baseName); static QString systemLogPath(); static bool createEmptyFile(const QString& path); static bool initializePath(const QString& path); diff --git a/deploy/data/linux/client/AmneziaVPN.desktop b/deploy/data/linux/client/AmneziaVPN.desktop new file mode 100644 index 00000000..a032dbce --- /dev/null +++ b/deploy/data/linux/client/AmneziaVPN.desktop @@ -0,0 +1,8 @@ +#!/usr/bin/env xdg-open +[Desktop Entry] +Type=Application +Name=AmneziaVPN client +Comment=AmneziaVPN client +Exec=bash -c 'export LD_LIBRARY_PATH=/opt/AmneziaVPN/client/lib/ ; AmneziaVPN' +Icon=AmneziaVPN_Logo.png +Categories=VPN diff --git a/deploy/data/linux/client/bin/openvpn/easyrsa b/deploy/data/linux/client/bin/openvpn/easyrsa new file mode 100644 index 00000000..e95c53ad --- /dev/null +++ b/deploy/data/linux/client/bin/openvpn/easyrsa @@ -0,0 +1,2579 @@ +#!/bin/sh + +# Easy-RSA 3 -- A Shell-based CA Utility +# +# Copyright (C) 2018 by the Open-Source OpenVPN development community. +# A full list of contributors can be found in the ChangeLog. +# +# This code released under version 2 of the GNU GPL; see COPYING and the +# Licensing/ directory of this project for full licensing details. + +# Help/usage output to stdout +usage() { + # command help: + print " +Easy-RSA 3 usage and overview + +USAGE: easyrsa [options] COMMAND [command-options] + +A list of commands is shown below. To get detailed usage and help for a +command, run: + ./easyrsa help COMMAND + +For a listing of options that can be supplied before the command, use: + ./easyrsa help options + +Here is the list of commands available with a short syntax reminder. Use the +'help' command above to get full usage details. + + init-pki + build-ca [ cmd-opts ] + gen-dh + gen-req [ cmd-opts ] + sign-req + build-client-full [ cmd-opts ] + build-server-full [ cmd-opts ] + revoke [cmd-opts] + renew [cmd-opts] + build-serverClient-full [ cmd-opts ] + gen-crl + update-db + show-req [ cmd-opts ] + show-cert [ cmd-opts ] + show-ca [ cmd-opts ] + import-req + export-p7 [ cmd-opts ] + export-p8 [ cmd-opts ] + export-p12 [ cmd-opts ] + set-rsa-pass [ cmd-opts ] + set-ec-pass [ cmd-opts ] + upgrade +" + + # collect/show dir status: + err_source="Not defined: vars autodetect failed and no value provided" + work_dir="${EASYRSA:-$err_source}" + pki_dir="${EASYRSA_PKI:-$err_source}" + print "\ +DIRECTORY STATUS (commands would take effect on these locations) + EASYRSA: $work_dir + PKI: $pki_dir +" +} # => usage() + +# Detailed command help +# When called with no args, calls usage(), otherwise shows help for a command +cmd_help() { + text="" + opts="" + case "$1" in + init-pki|clean-all) text=" + init-pki [ cmd-opts ] + Removes & re-initializes the PKI dir for a clean PKI" ;; + build-ca) text=" + build-ca [ cmd-opts ] + Creates a new CA" + opts=" + nopass - do not encrypt the CA key (default is encrypted) + subca - create an intermediate CA keypair and request (default is a root CA) + intca - alias to the above" ;; + gen-dh) text=" + gen-dh + Generates DH (Diffie-Hellman) parameters" ;; + gen-req) text=" + gen-req [ cmd-opts ] + Generate a standalone keypair and request (CSR) + + This request is suitable for sending to a remote CA for signing." + opts=" + nopass - do not encrypt the private key (default is encrypted)" ;; + sign|sign-req) text=" + sign-req + Sign a certificate request of the defined type. must be a known + type such as 'client', 'server', 'serverClient', or 'ca' (or a user-added type.) + + This request file must exist in the reqs/ dir and have a .req file + extension. See import-req below for importing reqs from other sources." ;; + build|build-client-full|build-server-full|build-serverClient-full) text=" + build-client-full [ cmd-opts ] + build-server-full [ cmd-opts ] + build-serverClient-full [ cmd-opts ] + Generate a keypair and sign locally for a client and/or server + + This mode uses the as the X509 CN." + opts=" + nopass - do not encrypt the private key (default is encrypted) + inline - create an inline credentials file for this node" ;; + revoke) text=" + revoke [reason] + Revoke a certificate specified by the filename_base, with an optional + revocation reason that is one of: + unspecified + keyCompromise + CACompromise + affiliationChanged + superseded + cessationOfOperation + certificateHold";; + renew) text=" + renew [ cmd-opts ] + Renew a certificate specified by the filename_base" + opts=" + nopass - do not encrypt the private key (default is encrypted)" ;; + gen-crl) text=" + gen-crl + Generate a CRL" ;; + update-db) text=" + update-db + Update the index.txt database + + This command will use the system time to update the status of issued + certificates." ;; + show-req|show-cert) text=" + show-req [ cmd-opts ] + show-cert [ cmd-opts ] + Shows details of the req or cert referenced by filename_base + + Human-readable output is shown, including any requested cert options when + showing a request." + opts=" + full - show full req/cert info, including pubkey/sig data" ;; + show-ca) text=" + show-ca [ cmd-opts ] + Shows details of the CA cert + + Human-readable output is shown." + opts=" + full - show full cert info, including pubkey/sig data" ;; + import-req) text=" + import-req + Import a certificate request from a file + + This will copy the specified file into the reqs/ dir in + preparation for signing. + The is the filename base to create. + + Example usage: + import-req /some/where/bob_request.req bob" ;; + export-p12) text=" + export-p12 [ cmd-opts ] + Export a PKCS#12 file with the keypair specified by " + opts=" + noca - do not include the ca.crt file in the PKCS12 output + nokey - do not include the private key in the PKCS12 output" ;; + export-p7) text=" + export-p7 [ cmd-opts ] + Export a PKCS#7 file with the pubkey specified by " + opts=" + noca - do not include the ca.crt file in the PKCS7 output" ;; + export-p8) text=" + export-p8 [ cmd-opts ] + Export a PKCS#8 file with the private key specified by " + opts=" + noca - do not include the ca.crt file in the PKCS7 output" ;; + set-rsa-pass|set-ec-pass) text=" + set-rsa-pass [ cmd-opts ] + set-ec-pass [ cmd-opts ] + Set a new passphrase on an RSA or EC key for the listed ." + opts=" + nopass - use no password and leave the key unencrypted + file - (advanced) treat the file as a raw path, not a short-name" ;; + upgrade) text=" + upgrade + Upgrade EasyRSA PKI and/or CA. must be one of: + pki - Upgrade EasyRSA v2.x PKI to EasyRSA v3.x PKI (includes CA below) + ca - Upgrade EasyRSA v3.0.5 CA or older to EasyRSA v3.0.6 CA or later." ;; + altname|subjectaltname|san) text=" + --subject-alt-name=SAN_FORMAT_STRING + This global option adds a subjectAltName to the request or issued + certificate. It MUST be in a valid format accepted by openssl or + req/cert generation will fail. Note that including multiple such names + requires them to be comma-separated; further invocations of this + option will REPLACE the value. + + Examples of the SAN_FORMAT_STRING shown below: + DNS:alternate.example.net + DNS:primary.example.net,DNS:alternate.example.net + IP:203.0.113.29 + email:alternate@example.net" ;; + options) + opt_usage ;; + "") + usage ;; + *) text=" + Unknown command: '$1' (try without commands for a list of commands)" ;; + esac + + # display the help text + print "$text" + [ -n "$opts" ] && print " + cmd-opts is an optional set of command options from this list: +$opts" +} # => cmd_help() + +# Options usage +opt_usage() { + print " +Easy-RSA Global Option Flags + +The following options may be provided before the command. Options specified +at runtime override env-vars and any 'vars' file in use. Unless noted, +non-empty values to options are mandatory. + +General options: + +--batch : set automatic (no-prompts when possible) mode +--passin=ARG : set -passin ARG for openssl +--passout=ARG : set -passout ARG for openssl +--pki-dir=DIR : declares the PKI directory +--vars=FILE : define a specific 'vars' file to use for Easy-RSA config +--version : prints EasyRSA version and build information, then exits + +Certificate & Request options: (these impact cert/req field values) + +--days=# : sets the signing validity to the specified number of days +--digest=ALG : digest to use in the requests & certificates +--dn-mode=MODE : DN mode to use (cn_only or org) +--keysize=# : size in bits of keypair to generate +--req-cn=NAME : default CN to use +--subca-len=# : path length of signed intermediate CA certs; must be >= 0 if used +--subject-alt-name : Add a subjectAltName. For more info and syntax, see: + ./easyrsa help altname +--use-algo=ALG : crypto alg to use: choose rsa (default) or ec +--curve=NAME : for elliptic curve, sets the named curve to use +--copy-ext : Copy included request X509 extensions (namely subjAltName + +Organizational DN options: (only used with the 'org' DN mode) + (values may be blank for org DN options) + +--req-c=CC : country code (2-letters) +--req-st=NAME : State/Province +--req-city=NAME : City/Locality +--req-org=NAME : Organization +--req-email=NAME : Email addresses +--req-ou=NAME : Organizational Unit + +Deprecated features: + +--ns-cert=YESNO : yes or no to including deprecated NS extensions +--ns-comment=COMMENT : NS comment to include (value may be blank) +" +} # => opt_usage() + +# Wrapper around printf - clobber print since it's not POSIX anyway +# shellcheck disable=SC1117 +print() { printf "%s\n" "$*" || exit 1; } + +# Exit fatally with a message to stderr +# present even with EASYRSA_BATCH as these are fatal problems +die() { + print " +Easy-RSA error: + +$1" 1>&2 + exit "${2:-1}" +} # => die() + +# non-fatal warning output +warn() { + [ ! "$EASYRSA_BATCH" ] && \ + print " +$1" 1>&2 + + return 0 +} # => warn() + +# informational notices to stdout +notice() { + [ ! "$EASYRSA_BATCH" ] && \ + print " +$1" + + return 0 +} # => notice() + +# yes/no case-insensitive match (operates on stdin pipe) +# Returns 0 when input contains yes, 1 for no, 2 for no match +# If both strings are present, returns 1; first matching line returns. +awk_yesno() { + #shellcheck disable=SC2016 + awkscript=' +BEGIN {IGNORECASE=1; r=2} +{ if(match($0,"no")) {r=1; exit} + if(match($0,"yes")) {r=0; exit} +} END {exit r}' + awk "$awkscript" +} # => awk_yesno() + +# intent confirmation helper func +# returns without prompting in EASYRSA_BATCH +confirm() { + [ "$EASYRSA_BATCH" ] && return + prompt="$1" + value="$2" + msg="$3" + input="" + print " +$msg + +Type the word '$value' to continue, or any other input to abort." + printf %s " $prompt" + #shellcheck disable=SC2162 + read input + [ "$input" = "$value" ] && return + notice "Aborting without confirmation." + exit 9 +} # => confirm() + +# mktemp wrapper +easyrsa_mktemp() { + [ -n "$EASYRSA_TEMP_DIR_session" ] || die "EASYRSA_TEMP_DIR_session not initialized!" + [ -d "$EASYRSA_TEMP_DIR_session" ] || mkdir -p "$EASYRSA_TEMP_DIR_session" || + die "Could not create temporary directory '$EASYRSA_TEMP_DIR_session'. Permission or concurrency problem?" + [ -d "$EASYRSA_TEMP_DIR_session" ] || die "Temporary directory '$EASYRSA_TEMP_DIR_session' does not exist" + + template="$EASYRSA_TEMP_DIR_session/tmp.XXXXXX" + tempfile=$(mktemp "$template") || return + + # win32 mktemp shipped by easyrsa returns template as file! + if [ "$template" = "$tempfile" ]; then + # but win32 mktemp -d does work + # but win32 mktemp -u does not work + tempfile=$(mktemp -du "$tempfile") || return + printf "" > "$tempfile" || return + fi + echo "$tempfile" +} # => easyrsa_mktemp + +# remove temp files and do terminal cleanups +cleanup() { + [ -z "$EASYRSA_TEMP_DIR_session" ] || rm -rf "$EASYRSA_TEMP_DIR_session" + # shellcheck disable=SC2039 + (stty echo 2>/dev/null) || { (set -o echo 2>/dev/null) && set -o echo; } + echo "" # just to get a clean line +} # => cleanup() + +easyrsa_openssl() { + openssl_command=$1; shift + + case $openssl_command in + makesafeconf) has_config=true;; + ca|req|srp|ts) has_config=true;; + *) has_config=false;; + esac + + if ! $has_config; then + "$EASYRSA_OPENSSL" "$openssl_command" "$@" + return + fi + + easyrsa_openssl_conf=$(easyrsa_mktemp) || die "Failed to create temporary file" + easyrsa_extra_exts= + if [ -n "$EASYRSA_EXTRA_EXTS" ]; then + easyrsa_extra_exts=$(easyrsa_mktemp) || die "Failed to create temporary file" + cat >"$easyrsa_extra_exts" <<-EOF + req_extensions = req_extra + [ req_extra ] + $EASYRSA_EXTRA_EXTS + EOF + fi + + # Make LibreSSL safe config file from OpenSSL config file + sed \ + -e "s\`ENV::EASYRSA\`EASYRSA\`g" \ + -e "s\`\$dir\`$EASYRSA_PKI\`g" \ + -e "s\`\$EASYRSA_PKI\`$EASYRSA_PKI\`g" \ + -e "s\`\$EASYRSA_CERT_EXPIRE\`$EASYRSA_CERT_EXPIRE\`g" \ + -e "s\`\$EASYRSA_CRL_DAYS\`$EASYRSA_CRL_DAYS\`g" \ + -e "s\`\$EASYRSA_DIGEST\`$EASYRSA_DIGEST\`g" \ + -e "s\`\$EASYRSA_KEY_SIZE\`$EASYRSA_KEY_SIZE\`g" \ + -e "s\`\$EASYRSA_DIGEST\`$EASYRSA_DIGEST\`g" \ + -e "s\`\$EASYRSA_DN\`$EASYRSA_DN\`g" \ + -e "s\`\$EASYRSA_REQ_COUNTRY\`$EASYRSA_REQ_COUNTRY\`g" \ + -e "s\`\$EASYRSA_REQ_PROVINCE\`$EASYRSA_REQ_PROVINCE\`g" \ + -e "s\`\$EASYRSA_REQ_CITY\`$EASYRSA_REQ_CITY\`g" \ + -e "s\`\$EASYRSA_REQ_ORG\`$EASYRSA_REQ_ORG\`g" \ + -e "s\`\$EASYRSA_REQ_OU\`$EASYRSA_REQ_OU\`g" \ + -e "s\`\$EASYRSA_REQ_CN\`$EASYRSA_REQ_CN\`g" \ + -e "s\`\$EASYRSA_REQ_EMAIL\`$EASYRSA_REQ_EMAIL\`g" \ + ${EASYRSA_EXTRA_EXTS:+-e "/^#%EXTRA_EXTS%/r $easyrsa_extra_exts"} \ + "$EASYRSA_SSL_CONF" > "$easyrsa_openssl_conf" || + die "Failed to update $easyrsa_openssl_conf" + + if [ "$openssl_command" = "makesafeconf" ]; then + cp "$easyrsa_openssl_conf" "$EASYRSA_SAFE_CONF" + err=$? + else + "$EASYRSA_OPENSSL" "$openssl_command" -config "$easyrsa_openssl_conf" "$@" + err=$? + fi + + rm -f "$easyrsa_openssl_conf" + rm -f "$easyrsa_extra_exts" + return $err +} # => easyrsa_openssl + +vars_source_check() { + # Check for defined EASYRSA_PKI + [ -n "$EASYRSA_PKI" ] || die "\ +EASYRSA_PKI env-var undefined" +} # => vars_source_check() + +# Verify supplied curve exists and generate curve file if needed +verify_curve_ec() { + if ! "$EASYRSA_OPENSSL" ecparam -name "$EASYRSA_CURVE" > /dev/null; then + die "\ +Curve $EASYRSA_CURVE not found. Run openssl ecparam -list_curves to show a +list of supported curves." + fi + + # Check that the ecparams dir exists + [ -d "$EASYRSA_EC_DIR" ] || mkdir "$EASYRSA_EC_DIR" || die "\ +Failed creating ecparams dir (permissions?) at: +$EASYRSA_EC_DIR" + + # Check that the required ecparams file exists + out="$EASYRSA_EC_DIR/${EASYRSA_CURVE}.pem" + [ -f "$out" ] && return 0 + "$EASYRSA_OPENSSL" ecparam -name "$EASYRSA_CURVE" -out "$out" || die "\ +Failed to generate ecparam file (permissions?) when writing to: +$out" + + # Explicitly return success for caller + return 0 +} + +# Verify if Edward Curve exists +verify_curve_ed() { + if [ "ed25519" = "$EASYRSA_CURVE" ] && "$EASYRSA_OPENSSL" genpkey -algorithm ED25519 > /dev/null; then + return 0 + elif [ "ed448" = "$EASYRSA_CURVE" ] && "$EASYRSA_OPENSSL" genpkey -algorithm ED448 > /dev/null; then + return 0 + fi + die "Curve $EASYRSA_CURVE not found." +} + +verify_ssl_lib () { + # Verify EASYRSA_OPENSSL command gives expected output + if [ -z "$EASYRSA_SSL_OK" ]; then + val="$("$EASYRSA_OPENSSL" version)" + case "${val%% *}" in + OpenSSL|LibreSSL) + print "\ +Using SSL: $EASYRSA_OPENSSL $("$EASYRSA_OPENSSL" version)" ;; + *) die "\ +Missing or invalid OpenSSL +Expected to find openssl command at: $EASYRSA_OPENSSL" ;; + esac + fi + EASYRSA_SSL_OK=1 + + # Verify EASYRSA_SSL_CONF file exists + [ -f "$EASYRSA_SSL_CONF" ] || die "\ +The OpenSSL config file cannot be found. +Expected location: $EASYRSA_SSL_CONF" +} # => verify_ssl_lib () + +# Basic sanity-check of PKI init and complain if missing +verify_pki_init() { + help_note="Run easyrsa without commands for usage and command help." + + # check that the pki dir exists + vars_source_check + [ -d "$EASYRSA_PKI" ] || die "\ +EASYRSA_PKI does not exist (perhaps you need to run init-pki)? +Expected to find the EASYRSA_PKI at: $EASYRSA_PKI +$help_note" + + # verify expected dirs present: + for i in private reqs; do + [ -d "$EASYRSA_PKI/$i" ] || die "\ +Missing expected directory: $i (perhaps you need to run init-pki?) +$help_note" + done + + # verify ssl lib + verify_ssl_lib +} # => verify_pki_init() + +# Verify core CA files present +verify_ca_init() { + help_note="Run without commands for usage and command help." + + # First check the PKI has been initialized + verify_pki_init + + # Verify expected files are present. Allow files to be regular files + # (or symlinks), but also pipes, for flexibility with ca.key + for i in serial index.txt index.txt.attr ca.crt private/ca.key; do + if [ ! -f "$EASYRSA_PKI/$i" ] && [ ! -p "$EASYRSA_PKI/$i" ]; then + [ "$1" = "test" ] && return 1 + die "\ +Missing expected CA file: $i (perhaps you need to run build-ca?) +$help_note" + fi + done + + # When operating in 'test' mode, return success. + # test callers don't care about CA-specific dir structure + [ "$1" = "test" ] && return 0 + + # verify expected CA-specific dirs: + for i in issued certs_by_serial + do + [ -d "$EASYRSA_PKI/$i" ] || die "\ +Missing expected CA dir: $i (perhaps you need to run build-ca?) +$help_note" + done + + # explicitly return success for callers + return 0 + +} # => verify_ca_init() + +# init-pki backend: +init_pki() { + + # If EASYRSA_PKI exists, confirm before we rm -rf (skiped with EASYRSA_BATCH) + if [ -e "$EASYRSA_PKI" ]; then + confirm "Confirm removal: " "yes" " +WARNING!!! + +You are about to remove the EASYRSA_PKI at: $EASYRSA_PKI +and initialize a fresh PKI here." + # now remove it: + rm -rf "$EASYRSA_PKI" || die "Removal of PKI dir failed. Check/correct errors above" + fi + + # new dirs: + for i in private reqs; do + mkdir -p "$EASYRSA_PKI/$i" || die "Failed to create PKI file structure (permissions?)" + done + + # Create $EASYRSA_SAFE_CONF ($OPENSSL_CONF) prevents bogus warnings (especially useful on win32) + if [ ! -f "$EASYRSA_SSL_CONF" ] && [ -f "$EASYRSA/openssl-easyrsa.cnf" ]; + then + cp "$EASYRSA/openssl-easyrsa.cnf" "$EASYRSA_SSL_CONF" + easyrsa_openssl makesafeconf + fi + + notice "\ +init-pki complete; you may now create a CA or requests. +Your newly created PKI dir is: $EASYRSA_PKI +" + return 0 +} # => init_pki() + +hide_read_pass() +{ + # shellcheck disable=SC2039 + if stty -echo 2>/dev/null; then + read -r "$@" + stty echo + elif (set +o echo 2>/dev/null); then + set +o echo + read -r "$@" + set -o echo + elif (echo | read -r -s 2>/dev/null) ; then + read -r -s "$@" + else + warn "Could not disable echo. Password will be shown on screen!" + read -r "$@" + fi +} # => hide_read_pass() + +# build-ca backend: +build_ca() { + opts="" + sub_ca="" + nopass="" + crypto="-aes256" + while [ -n "$1" ]; do + case "$1" in + intca) sub_ca=1 ;; + subca) sub_ca=1 ;; + nopass) nopass=1 ;; + *) warn "Ignoring unknown command option: '$1'" ;; + esac + shift + done + + verify_pki_init + [ "$EASYRSA_ALGO" = "ec" ] && verify_curve_ec + [ "$EASYRSA_ALGO" = "ed" ] && verify_curve_ed + + # setup for the simpler intermediate CA situation and overwrite with root-CA if needed: + out_file="$EASYRSA_PKI/reqs/ca.req" + out_key="$EASYRSA_PKI/private/ca.key" + if [ ! $sub_ca ]; then + out_file="$EASYRSA_PKI/ca.crt" + opts="$opts -x509 -days $EASYRSA_CA_EXPIRE " + fi + + # Test for existing CA, and complain if already present + if verify_ca_init test; then + die "\ +Unable to create a CA as you already seem to have one set up. +If you intended to start a new CA, run init-pki first." + fi + # If a private key exists here, a intermediate ca was created but not signed. + # Notify the user and require a signed ca.crt or a init-pki: + [ -f "$out_key" ] && \ + die "\ +A CA private key exists but no ca.crt is found in your PKI dir of: +$EASYRSA_PKI +Refusing to create a new CA keypair as this operation would overwrite your +current CA keypair. If you intended to start a new CA, run init-pki first." + + # create necessary files and dirs: + err_file="Unable to create necessary PKI files (permissions?)" + for i in issued certs_by_serial \ + revoked/certs_by_serial revoked/private_by_serial revoked/reqs_by_serial \ + renewed/certs_by_serial renewed/private_by_serial renewed/reqs_by_serial; + do + mkdir -p "$EASYRSA_PKI/$i" || die "$err_file" + done + printf "" > "$EASYRSA_PKI/index.txt" || die "$err_file" + printf "" > "$EASYRSA_PKI/index.txt.attr" || die "$err_file" + print "01" > "$EASYRSA_PKI/serial" || die "$err_file" + + # Default CN only when not in global EASYRSA_BATCH mode: + # shellcheck disable=SC2015 + [ "$EASYRSA_BATCH" ] && opts="$opts -batch" || export EASYRSA_REQ_CN="Easy-RSA CA" + + out_key_tmp="$(easyrsa_mktemp)" || die "Failed to create temporary file" + out_file_tmp="$(easyrsa_mktemp)" || die "Failed to create temporary file" + # Get password from user if necessary + if [ ! $nopass ] && ( [ -z "$EASYRSA_PASSOUT" ] || [ -z "$EASYRSA_PASSIN" ] ); then + out_key_pass_tmp="$(easyrsa_mktemp)" || die "Failed to create temporary file" + echo + printf "Enter New CA Key Passphrase: " + hide_read_pass kpass + echo + printf "Re-Enter New CA Key Passphrase: " + hide_read_pass kpass2 + echo + # shellcheck disable=2154 + if [ "$kpass" = "$kpass2" ]; + then + printf "%s" "$kpass" > "$out_key_pass_tmp" + else + die "Passphrases do not match." + fi + fi + + # create the CA key using AES256 + crypto_opts="" + if [ ! $nopass ]; then + crypto_opts="$crypto" + if [ -z "$EASYRSA_PASSOUT" ]; then + if [ "ed" = "$EASYRSA_ALGO" ]; then + crypto_opts="$crypto_opts -pass file:$out_key_pass_tmp" + else + crypto_opts="$crypto_opts -passout file:$out_key_pass_tmp" + fi + fi + fi + if [ "$EASYRSA_ALGO" = "rsa" ]; then + #shellcheck disable=SC2086 + "$EASYRSA_OPENSSL" genrsa -out "$out_key_tmp" $crypto_opts ${EASYRSA_PASSOUT:+-passout "$EASYRSA_PASSOUT"} "$EASYRSA_ALGO_PARAMS" || \ + die "Failed create CA private key" + elif [ "$EASYRSA_ALGO" = "ec" ]; then + #shellcheck disable=SC2086 + "$EASYRSA_OPENSSL" ecparam -in "$EASYRSA_ALGO_PARAMS" -genkey | \ + "$EASYRSA_OPENSSL" ec -out "$out_key_tmp" $crypto_opts ${EASYRSA_PASSOUT:+-passout "$EASYRSA_PASSOUT"} || \ + die "Failed create CA private key" + elif [ "ed" = "$EASYRSA_ALGO" ]; then + if [ "ed25519" = "$EASYRSA_CURVE" ]; then + "$EASYRSA_OPENSSL" genpkey -algorithm ED25519 -out $out_key_tmp $crypto_opts ${EASYRSA_PASSOUT:+-pass "$EASYRSA_PASSOUT"} || \ + die "Failed create CA private key" + elif [ "ed448" = "$EASYRSA_CURVE" ]; then + "$EASYRSA_OPENSSL" genpkey -algorithm ED448 -out $out_key_tmp $crypto_opts ${EASYRSA_PASSOUT:+-pass "$EASYRSA_PASSOUT"} || \ + die "Failed create CA private key" + fi + fi + + # create the CA keypair: + crypto_opts="" + [ ! $nopass ] && [ -z "$EASYRSA_PASSIN" ] && crypto_opts="-passin file:$out_key_pass_tmp" + + #shellcheck disable=SC2086 + easyrsa_openssl req -utf8 -new -key "$out_key_tmp" \ + -keyout "$out_key_tmp" -out "$out_file_tmp" $crypto_opts $opts ${EASYRSA_PASSIN:+-passin "$EASYRSA_PASSIN"} || \ + die "Failed to build the CA" + + mv "$out_key_tmp" "$out_key" + mv "$out_file_tmp" "$out_file" + [ -f "$out_key_pass_tmp" ] && rm "$out_key_pass_tmp" + + # Success messages + if [ $sub_ca ]; then + notice "\ +NOTE: Your intermediate CA request is at $out_file +and now must be sent to your parent CA for signing. Place your resulting cert +at $EASYRSA_PKI/ca.crt prior to signing operations. +" + else notice "\ +CA creation complete and you may now import and sign cert requests. +Your new CA certificate file for publishing is at: +$out_file +" + fi + return 0 +} # => build_ca() + +# gen-dh backend: +gen_dh() { + verify_pki_init + + out_file="$EASYRSA_PKI/dh.pem" + + # check to see if we already have a dh parameters file + if [ -e "$EASYRSA_PKI/dh.pem" ]; then + if [ "$EASYRSA_BATCH" = "1" ]; then + # if batch is enabled, die + die "file $EASYRSA_PKI/dh.pem already exists!" + else + # warn the user, give them a chance to force overwrite + confirm "Overwrite? " "yes" "*** File $EASYRSA_PKI/dh.pem already exists! ***" + fi + fi + + "$EASYRSA_OPENSSL" dhparam -out "$out_file" "$EASYRSA_KEY_SIZE" || \ + die "Failed to build DH params" + notice "\ +DH parameters of size $EASYRSA_KEY_SIZE created at $out_file +" + return 0 +} # => gen_dh() + +# gen-req backend: +gen_req() { + # pull filename base and use as default interactive CommonName: + [ -n "$1" ] || die "\ +Error: gen-req must have a file base as the first argument. +Run easyrsa without commands for usage and commands." + key_out="$EASYRSA_PKI/private/$1.key" + req_out="$EASYRSA_PKI/reqs/$1.req" + [ ! "$EASYRSA_BATCH" ] && EASYRSA_REQ_CN="$1" + shift + + # function opts support + opts= + while [ -n "$1" ]; do + case "$1" in + nopass) opts="$opts -nodes" ;; + # batch flag supports internal callers needing silent operation + batch) EASYRSA_BATCH=1 ;; + *) warn "Ignoring unknown command option: '$1'" ;; + esac + shift + done + + verify_pki_init + [ "$EASYRSA_ALGO" = "ec" ] && verify_curve_ec + [ "$EASYRSA_ALGO" = "ed" ] && verify_curve_ed + + # don't wipe out an existing private key without confirmation + [ -f "$key_out" ] && confirm "Confirm key overwrite: " "yes" "\ + +WARNING!!! + +An existing private key was found at $key_out +Continuing with key generation will replace this key." + + # When EASYRSA_EXTRA_EXTS is defined, append it to openssl's [req] section: + if [ -n "$EASYRSA_EXTRA_EXTS" ]; then + # Setup & insert the extra ext data keyed by a magic line + extra_exts=" +req_extensions = req_extra +[ req_extra ] +$EASYRSA_EXTRA_EXTS" + #shellcheck disable=SC2016 + awkscript=' +{if ( match($0, "^#%EXTRA_EXTS%") ) + { while ( getline<"/dev/stdin" ) {print} next } + {print} +}' + conf_tmp="$(easyrsa_mktemp)" || die "Failed to create temporary file" + print "$extra_exts" | \ + awk "$awkscript" "$EASYRSA_SSL_CONF" \ + > "$conf_tmp" \ + || die "Copying SSL config to temp file failed" + # Use this new SSL config for the rest of this function + EASYRSA_SSL_CONF="$conf_tmp" + fi + + key_out_tmp="$(easyrsa_mktemp)" || die "Failed to create temporary file" + req_out_tmp="$(easyrsa_mktemp)" || die "Failed to create temporary file" + # generate request + [ $EASYRSA_BATCH ] && opts="$opts -batch" + # shellcheck disable=2086,2148 + algo_opts="" + if [ "ed" = "$EASYRSA_ALGO" ]; then + algo_opts=" -newkey $EASYRSA_CURVE " + else + algo_opts=" -newkey $EASYRSA_ALGO:$EASYRSA_ALGO_PARAMS " + fi + easyrsa_openssl req -utf8 -new $algo_opts \ + -keyout "$key_out_tmp" -out "$req_out_tmp" $opts ${EASYRSA_PASSOUT:+-passout "$EASYRSA_PASSOUT"} \ + || die "Failed to generate request" + mv "$key_out_tmp" "$key_out" + mv "$req_out_tmp" "$req_out" + notice "\ +Keypair and certificate request completed. Your files are: +req: $req_out +key: $key_out +" + return 0 +} # => gen_req() + +# common signing backend +sign_req() { + crt_type="$1" + opts="" + req_in="$EASYRSA_PKI/reqs/$2.req" + crt_out="$EASYRSA_PKI/issued/$2.crt" + + # Randomize Serial number + if [ "$EASYRSA_RAND_SN" != "no" ]; + then + i="" + serial="" + check_serial="" + for i in 1 2 3 4 5; do + "$EASYRSA_OPENSSL" rand -hex -out "$EASYRSA_PKI/serial" 16 + serial="$(cat "$EASYRSA_PKI/serial")" + check_serial="$("$EASYRSA_OPENSSL" ca -config "$EASYRSA_SSL_CONF" -status "$serial" 2>&1)" + case "$check_serial" in + *"not present in db"*) break ;; + *) continue ;; + esac + done + fi + + # Support batch by internal caller: + [ "$3" = "batch" ] && EASYRSA_BATCH=1 + + verify_ca_init + + # Check argument sanity: + [ -n "$2" ] || die "\ +Incorrect number of arguments provided to sign-req: +expected 2, got $# (see command help for usage)" + + # Cert type must exist under the EASYRSA_EXT_DIR + [ -r "$EASYRSA_EXT_DIR/$crt_type" ] || die "\ +Unknown cert type '$crt_type'" + + # Request file must exist + [ -f "$req_in" ] || die "\ +No request found for the input: '$2' +Expected to find the request at: $req_in" + + # Confirm input is a cert req + verify_file req "$req_in" || die "\ +The certificate request file is not in a valid X509 request format. +File Path: $req_in" + + # Display the request subject in an easy-to-read format + # Confirm the user wishes to sign this request + confirm "Confirm request details: " "yes" " +You are about to sign the following certificate. +Please check over the details shown below for accuracy. Note that this request +has not been cryptographically verified. Please be sure it came from a trusted +source or that you have verified the request checksum with the sender. + +Request subject, to be signed as a $crt_type certificate for $EASYRSA_CERT_EXPIRE days: + +$(display_dn req "$req_in") +" # => confirm end + + # Generate the extensions file for this cert: + ext_tmp="$(easyrsa_mktemp)" || die "Failed to create temporary file" + { + # Append first any COMMON file (if present) then the cert-type extensions + cat "$EASYRSA_EXT_DIR/COMMON" + cat "$EASYRSA_EXT_DIR/$crt_type" + # copy req extensions + [ "$EASYRSA_CP_EXT" ] && print "copy_extensions = copy" + + # Support a dynamic CA path length when present: + [ "$crt_type" = "ca" ] && [ -n "$EASYRSA_SUBCA_LEN" ] && \ + print "basicConstraints = CA:TRUE, pathlen:$EASYRSA_SUBCA_LEN" + + # Deprecated Netscape extension support, if enabled + if print "$EASYRSA_NS_SUPPORT" | awk_yesno; then + [ -n "$EASYRSA_NS_COMMENT" ] && \ + print "nsComment = \"$EASYRSA_NS_COMMENT\"" + case "$crt_type" in + serverClient) print "nsCertType = serverClient" ;; + server) print "nsCertType = server" ;; + client) print "nsCertType = client" ;; + ca) print "nsCertType = sslCA" ;; + esac + fi + + # If type is server and no subjectAltName was requested, + # add one to the extensions file + if [ "$crt_type" = 'server' ] || [ "$crt_type" = 'serverClient' ]; + then + echo "$EASYRSA_EXTRA_EXTS" | grep -q subjectAltName + if [ $? -ne 0 ]; + then + san=$(display_san req "$req_in") + + if [ -n "$san" ]; + then + print "subjectAltName = $san" + else + default_server_san "$req_in" + fi + fi + fi + + # Add any advanced extensions supplied by env-var: + [ -n "$EASYRSA_EXTRA_EXTS" ] && print "$EASYRSA_EXTRA_EXTS" + + : # needed to keep die from inherting the above test + } > "$ext_tmp" || die "\ +Failed to create temp extension file (bad permissions?) at: +$ext_tmp" + + # sign request + crt_out_tmp="$(easyrsa_mktemp)" || die "Failed to create temporary file" + easyrsa_openssl ca -utf8 -in "$req_in" -out "$crt_out_tmp" \ + -extfile "$ext_tmp" -days "$EASYRSA_CERT_EXPIRE" -batch $opts ${EASYRSA_PASSIN:+-passin "$EASYRSA_PASSIN"} \ + || die "signing failed (openssl output above may have more detail)" + mv "$crt_out_tmp" "$crt_out" + rm -f "$ext_tmp" + notice "\ +Certificate created at: $crt_out +" + return 0 +} # => sign_req() + +# common build backend +# used to generate+sign in 1 step +build_full() { + verify_ca_init + + # pull filename base: + [ -n "$2" ] || die "\ +Error: didn't find a file base name as the first argument. +Run easyrsa without commands for usage and commands." + crt_type="$1" name="$2" + req_out="$EASYRSA_PKI/reqs/$2.req" + key_out="$EASYRSA_PKI/private/$2.key" + crt_out="$EASYRSA_PKI/issued/$2.crt" + shift 2 + + # function opts support + req_opts= + while [ -n "$1" ]; do + case "$1" in + nopass) req_opts="$req_opts nopass" ;; + inline) EASYRSA_INLINE=1 ;; + *) warn "Ignoring unknown command option: '$1'" ;; + esac + shift + done + + # abort on existing req/key/crt files + err_exists="\ +file already exists. Aborting build to avoid overwriting this file. +If you wish to continue, please use a different name or remove the file. +Matching file found at: " + [ -f "$req_out" ] && die "Request $err_exists $req_out" + [ -f "$key_out" ] && die "Key $err_exists $key_out" + [ -f "$crt_out" ] && die "Certificate $err_exists $crt_out" + + # create request + EASYRSA_REQ_CN="$name" + #shellcheck disable=SC2086 + gen_req "$name" batch $req_opts + + # Sign it + ( sign_req "$crt_type" "$name" batch ) || { + rm -f "$req_out" "$key_out" + die "Failed to sign '$name'" + } + + # inline it + if [ $EASYRSA_INLINE ]; then + inline_creds + fi +} # => build_full() + +# Create inline credentials file for this node +inline_creds () +{ + [ -f "$EASYRSA_PKI/$EASYRSA_REQ_CN.creds" ] \ + && die "Inline file exists: $EASYRSA_PKI/$EASYRSA_REQ_CN.creds" + { + printf "%s\n" "# $crt_type: $EASYRSA_REQ_CN" + printf "%s\n" "" + printf "%s\n" "" + cat "$EASYRSA_PKI/ca.crt" + printf "%s\n" "" + printf "%s\n" "" + printf "%s\n" "" + cat "$crt_out" + printf "%s\n" "" + printf "%s\n" "" + printf "%s\n" "" + cat "$key_out" + printf "%s\n" "" + printf "%s\n" "" + } > "$EASYRSA_PKI/$EASYRSA_REQ_CN.creds" +} # => inline_creds () + +# revoke backend +revoke() { + verify_ca_init + + # pull filename base: + [ -n "$1" ] || die "\ +Error: didn't find a file base name as the first argument. +Run easyrsa without commands for usage and command help." + crt_in="$EASYRSA_PKI/issued/$1.crt" + + opts="" + if [ "$2" ]; then + opts="$opts -crl_reason $2" + fi + + verify_file x509 "$crt_in" || die "\ +Unable to revoke as the input file is not a valid certificate. Unexpected +input in file: $crt_in" + + # confirm operation by displaying DN: + confirm "Continue with revocation: " "yes" " +Please confirm you wish to revoke the certificate with the following subject: + +$(display_dn x509 "$crt_in") +" # => confirm end + + # referenced cert must exist: + [ -f "$crt_in" ] || die "\ +Unable to revoke as no certificate was found. Certificate was expected +at: $crt_in" + + # shellcheck disable=SC2086 + easyrsa_openssl ca -utf8 -revoke "$crt_in" ${EASYRSA_PASSIN:+-passin "$EASYRSA_PASSIN"} $opts || die "\ +Failed to revoke certificate: revocation command failed." + + # move revoked files so we can reissue certificates with the same name + move_revoked "$1" + + notice "\ +IMPORTANT!!! + +Revocation was successful. You must run gen-crl and upload a CRL to your +infrastructure in order to prevent the revoked cert from being accepted. +" # => notice end + return 0 +} #= revoke() + +# move-revoked +# moves revoked certificates to an alternative folder +# allows reissuing certificates with the same name +move_revoked() { + verify_ca_init + + [ -n "$1" ] || die "\ +Error: didn't find a file base name as the first argument. +Run easyrsa without commands for usage and command help." + + crt_in="$EASYRSA_PKI/issued/$1.crt" + key_in="$EASYRSA_PKI/private/$1.key" + req_in="$EASYRSA_PKI/reqs/$1.req" + + verify_file x509 "$crt_in" || die "\ +Unable to move revoked input file. The file is not a valid certificate. Unexpected +input in file: $crt_in" + + if [ -e "$req_in" ] + then + verify_file req "$req_in" || die "\ +Unable to move request. The file is not a valid request. Unexpected +input in file: $req_in" + fi + + # get the serial number of the certificate -> serial=XXXX + cert_serial="$(easyrsa_openssl x509 -in "$crt_in" -noout -serial)" + # remove the serial= part -> we only need the XXXX part + cert_serial=${cert_serial##*=} + + crt_by_serial="$EASYRSA_PKI/certs_by_serial/$cert_serial.pem" + crt_by_serial_revoked="$EASYRSA_PKI/revoked/certs_by_serial/$cert_serial.crt" + key_by_serial_revoked="$EASYRSA_PKI/revoked/private_by_serial/$cert_serial.key" + req_by_serial_revoked="$EASYRSA_PKI/revoked/reqs_by_serial/$cert_serial.req" + + # make sure revoked dirs exist + [ -d "$EASYRSA_PKI/revoked" ] || mkdir "$EASYRSA_PKI/revoked" + [ -d "$EASYRSA_PKI/revoked/certs_by_serial" ] || mkdir "$EASYRSA_PKI/revoked/certs_by_serial" + [ -d "$EASYRSA_PKI/revoked/private_by_serial" ] || mkdir "$EASYRSA_PKI/revoked/private_by_serial" + [ -d "$EASYRSA_PKI/revoked/reqs_by_serial" ] || mkdir "$EASYRSA_PKI/revoked/reqs_by_serial" + + # move crt, key and req file to revoked folders + mv "$crt_in" "$crt_by_serial_revoked" + + # only move the req if we have it + [ -e "$req_in" ] && mv "$req_in" "$req_by_serial_revoked" + + # only move the key if we have it + [ -e "$key_in" ] && mv "$key_in" "$key_by_serial_revoked" + + # move the rest of the files (p12, p7, ...) + # shellcheck disable=SC2231 + for file in $EASYRSA_PKI/private/$1\.??? + do + # get file extension + file_ext="${file##*.}" + + [ -f "$file" ] && mv "$file" "$EASYRSA_PKI/revoked/private_by_serial/$cert_serial.$file_ext" + done + + # remove the dublicate certificate in the certs_by_serial folder + rm "$crt_by_serial" + + return 0 + +} #= move_revoked() + +# renew backend +renew() { + verify_ca_init + + # pull filename base: + [ -n "$1" ] || die "\ +Error: didn't find a file base name as the first argument. +Run easyrsa without commands for usage and command help." + crt_in="$EASYRSA_PKI/issued/$1.crt" + + opts="" + if [ "$2" ]; then + opts="$2" + fi + + verify_file x509 "$crt_in" || die "\ +Unable to renew as the input file is not a valid certificate. Unexpected +input in file: $crt_in" + + # confirm operation by displaying DN: + confirm "Continue with renew: " "yes" " +Please confirm you wish to renew the certificate with the following subject: + +$(display_dn x509 "$crt_in") +" # => confirm end + + # referenced cert must exist: + [ -f "$crt_in" ] || die "\ +Unable to renew as no certificate was found. Certificate was expected +at: $crt_in" + + # Check if old cert is expired or expires within 30 days + expire_date=$( + easyrsa_openssl x509 -in "$crt_in" -noout -enddate | + sed 's/^notAfter=//' + ) + case $(uname 2>/dev/null) in + "Darwin"|*"BSD") + expire_date=$(date -j -f '%b %d %T %Y %Z' "$expire_date" +%s) + allow_renew_date=$(date -j -v"+${EASYRSA_CERT_RENEW}d" +%s) + ;; + *) + # This works on Windows, too, since uname doesn't exist and this is catch-all + expire_date=$(date -d "$expire_date" +%s) + allow_renew_date=$(date -d "+${EASYRSA_CERT_RENEW}day" +%s) + ;; + esac + + [ "$expire_date" -lt "$allow_renew_date" ] || die "\ +Certificate expires in more than $EASYRSA_CERT_RENEW days. +Renewal not allowed." + + # Extract certificate usage from old cert + cert_ext_key_usage=$( + easyrsa_openssl x509 -in "$crt_in" -noout -text | + sed -n "/X509v3 Extended Key Usage:/{n;s/^ *//g;p;}" + ) + case $cert_ext_key_usage in + "TLS Web Client Authentication") + cert_type=client + ;; + "TLS Web Server Authentication") + cert_type=server + ;; + "TLS Web Server Authentication, TLS Web Client Authentication") + cert_type=serverClient + ;; + esac + + # Use SAN from --subject-alt-name if set else use SAN from old cert + echo "$EASYRSA_EXTRA_EXTS" | grep -q subjectAltName || \ + { + san=$( + easyrsa_openssl x509 -in "$crt_in" -noout -text | + sed -n "/X509v3 Subject Alternative Name:/{n;s/IP Address:/IP:/;s/ //g;p;}" + ) + [ -n "$san" ] && export EASYRSA_EXTRA_EXTS="\ +$EASYRSA_EXTRA_EXTS +subjectAltName = $san" + } + + # move renewed files so we can reissue certificate with the same name + # FIXME: Modify revoke() to also work on the renewed certs subdir + move_renewed "$1" + + # renew certificate + # shellcheck disable=SC2086 + build_full $cert_type $1 $opts || die "\ +Failed to renew certificate: renew command failed." + + notice "\ +IMPORTANT!!! + +Renew was successful. +You may want to revoke the old certificate once the new one has been deployed. +" # => notice end + return 0 +} #= renew() + +# move-renewed +# moves renewed certificates to an alternative folder +# allows reissuing certificates with the same name +move_renewed() { + verify_ca_init + + [ -n "$1" ] || die "\ +Error: didn't find a file base name as the first argument. +Run easyrsa without commands for usage and command help." + + crt_in="$EASYRSA_PKI/issued/$1.crt" + key_in="$EASYRSA_PKI/private/$1.key" + req_in="$EASYRSA_PKI/reqs/$1.req" + + verify_file x509 "$crt_in" || die "\ +Unable to move renewed input file. The file is not a valid certificate. Unexpected +input in file: $crt_in" + + if [ -e "$req_in" ] + then + verify_file req "$req_in" || die "\ +Unable to move request. The file is not a valid request. Unexpected +input in file: $req_in" + fi + + # get the serial number of the certificate -> serial=XXXX + cert_serial="$(easyrsa_openssl x509 -in "$crt_in" -noout -serial)" + # remove the serial= part -> we only need the XXXX part + cert_serial=${cert_serial##*=} + + crt_by_serial="$EASYRSA_PKI/certs_by_serial/$cert_serial.pem" + crt_by_serial_renewed="$EASYRSA_PKI/renewed/certs_by_serial/$cert_serial.crt" + key_by_serial_renewed="$EASYRSA_PKI/renewed/private_by_serial/$cert_serial.key" + req_by_serial_renewed="$EASYRSA_PKI/renewed/reqs_by_serial/$cert_serial.req" + + # make sure renewed dirs exist + [ -d "$EASYRSA_PKI/renewed" ] || mkdir "$EASYRSA_PKI/renewed" + [ -d "$EASYRSA_PKI/renewed/certs_by_serial" ] || mkdir "$EASYRSA_PKI/renewed/certs_by_serial" + [ -d "$EASYRSA_PKI/renewed/private_by_serial" ] || mkdir "$EASYRSA_PKI/renewed/private_by_serial" + [ -d "$EASYRSA_PKI/renewed/reqs_by_serial" ] || mkdir "$EASYRSA_PKI/renewed/reqs_by_serial" + + # move crt, key and req file to renewed folders + mv "$crt_in" "$crt_by_serial_renewed" + + # only move the req if we have it + [ -e "$req_in" ] && mv "$req_in" "$req_by_serial_renewed" + + # only move the key if we have it + [ -e "$key_in" ] && mv "$key_in" "$key_by_serial_renewed" + + # move the rest of the files (p12, p7, ...) + # shellcheck disable=SC2231 + for file in $EASYRSA_PKI/private/$1\.??? + do + # get file extension + file_ext="${file##*.}" + + [ -f "$file" ] && mv "$file" "$EASYRSA_PKI/renewed/private_by_serial/$cert_serial.$file_ext" + done + + # remove the duplicate certificate in the certs_by_serial folder + rm "$crt_by_serial" + + return 0 + +} #= move_renewed() + +# gen-crl backend +gen_crl() { + verify_ca_init + + out_file="$EASYRSA_PKI/crl.pem" + out_file_tmp="$(easyrsa_mktemp)" || die "Failed to create temporary file" + easyrsa_openssl ca -utf8 -gencrl -out "$out_file_tmp" ${EASYRSA_PASSIN:+-passin "$EASYRSA_PASSIN"} || die "\ +CRL Generation failed. +" + mv "$out_file_tmp" "$out_file" + + notice "\ +An updated CRL has been created. +CRL file: $out_file +" + return 0 +} # => gen_crl() + +# import-req backend +import_req() { + verify_pki_init + + # pull passed paths + in_req="$1" short_name="$2" + out_req="$EASYRSA_PKI/reqs/$2.req" + + [ -n "$short_name" ] || die "\ +Unable to import: incorrect command syntax. +Run easyrsa without commands for usage and command help." + + verify_file req "$in_req" || die "\ +The input file does not appear to be a certificate request. Aborting import. +File Path: $in_req" + + # destination must not exist + [ -f "$out_req" ] && die "\ +Unable to import the request as the destination file already exists. +Please choose a different name for your imported request file. +Existing file at: $out_req" + + # now import it + cp "$in_req" "$out_req" + + notice "\ +The request has been successfully imported with a short name of: $short_name +You may now use this name to perform signing operations on this request. +" + return 0 +} # => import_req() + +# export pkcs#12 or pkcs#7 +export_pkcs() { + pkcs_type="$1" + shift + + [ -n "$1" ] || die "\ +Unable to export p12: incorrect command syntax. +Run easyrsa without commands for usage and command help." + + short_name="$1" + crt_in="$EASYRSA_PKI/issued/$1.crt" + key_in="$EASYRSA_PKI/private/$1.key" + crt_ca="$EASYRSA_PKI/ca.crt" + shift + + verify_pki_init + + # opts support + want_ca=1 + want_key=1 + want_pass=1 + while [ -n "$1" ]; do + case "$1" in + noca) want_ca="" ;; + nokey) want_key="" ;; + nopass) want_pass="" ;; + *) warn "Ignoring unknown command option: '$1'" ;; + esac + shift + done + + pkcs_opts= + if [ $want_ca ]; then + verify_file x509 "$crt_ca" || die "\ +Unable to include CA cert in the $pkcs_type output (missing file, or use noca option.) +Missing file expected at: $crt_ca" + pkcs_opts="$pkcs_opts -certfile $crt_ca" + fi + + # input files must exist + verify_file x509 "$crt_in" || die "\ +Unable to export $pkcs_type for short name '$short_name' without the certificate. +Missing cert expected at: $crt_in" + + case "$pkcs_type" in + p12) + pkcs_out="$EASYRSA_PKI/private/$short_name.p12" + + if [ $want_key ]; then + [ -f "$key_in" ] || die "\ +Unable to export p12 for short name '$short_name' without the key +(if you want a p12 without the private key, use nokey option.) +Missing key expected at: $key_in" + else + pkcs_opts="$pkcs_opts -nokeys" + fi + + # export the p12: + # shellcheck disable=SC2086 + easyrsa_openssl pkcs12 -in "$crt_in" -inkey "$key_in" -export \ + -out "$pkcs_out" $pkcs_opts ${EASYRSA_PASSIN:+-passin "$EASYRSA_PASSIN"} ${EASYRSA_PASSOUT:+-passout "$EASYRSA_PASSOUT"} || die "\ +Export of p12 failed: see above for related openssl errors." + ;; + p7) + pkcs_out="$EASYRSA_PKI/issued/$short_name.p7b" + + # export the p7: + # shellcheck disable=SC2086 + easyrsa_openssl crl2pkcs7 -nocrl -certfile "$crt_in" \ + -out "$pkcs_out" $pkcs_opts ${EASYRSA_PASSIN:+-passin "$EASYRSA_PASSIN"} ${EASYRSA_PASSOUT:+-passout "$EASYRSA_PASSOUT"} || die "\ +Export of p7 failed: see above for related openssl errors." + ;; + p8) + if [ -z $want_pass ]; then + pkcs_opts="-nocrypt" + else + pkcs_opts="" + fi + pkcs_out="$EASYRSA_PKI/private/$short_name.p8" + + # export the p8: + # shellcheck disable=SC2086 + easyrsa_openssl pkcs8 -in "$key_in" -topk8 \ + -out "$pkcs_out" $pkcs_opts ${EASYRSA_PASSIN:+-passin "$EASYRSA_PASSIN"} ${EASYRSA_PASSOUT:+-passout "$EASYRSA_PASSOUT"} || die "\ +Export of p8 failed: see above for related openssl errors." + ;; +esac + + notice "\ +Successful export of $pkcs_type file. Your exported file is at the following +location: $pkcs_out +" + return 0 +} # => export_pkcs() + +# set-pass backend +set_pass() { + verify_pki_init + + # key type, supplied internally from frontend command call (rsa/ec) + key_type="$1" + + # values supplied by the user: + raw_file="$2" + file="$EASYRSA_PKI/private/$raw_file.key" + [ -n "$raw_file" ] || die "\ +Missing argument to 'set-$key_type-pass' command: no name/file supplied. +See help output for usage details." + + # parse command options + shift 2 + crypto="-aes256" + while [ -n "$1" ]; do + case "$1" in + nopass) crypto="" ;; + file) file="$raw_file" ;; + *) warn "Ignoring unknown command option: '$1'" ;; + esac + shift + done + + [ -f "$file" ] || die "\ +Missing private key: expected to find the private key component at: +$file" + + notice "\ +If the key is currently encrypted you must supply the decryption passphrase. +${crypto:+You will then enter a new PEM passphrase for this key.$NL}" + + out_key_tmp="$(easyrsa_mktemp)" || die "Failed to create temporary file" + easyrsa_openssl "$key_type" -in "$file" -out "$out_key_tmp" $crypto ${EASYRSA_PASSIN:+-passin "$EASYRSA_PASSIN"} ${EASYRSA_PASSOUT:+-passout "$EASYRSA_PASSOUT"} || die "\ +Failed to change the private key passphrase. See above for possible openssl +error messages." + + mv "$out_key_tmp" "$file" || die "\ +Failed to change the private key passphrase. See above for error messages." + + notice "Key passphrase successfully changed" + + return 0 +} # => set_pass() + +# update-db backend +update_db() { + verify_ca_init + + easyrsa_openssl ca -utf8 -updatedb ${EASYRSA_PASSIN:+-passin "$EASYRSA_PASSIN"} || die "\ +Failed to perform update-db: see above for related openssl errors." + return 0 +} # => update_db() + +display_san() { + format="$1" path="$2" + + echo "$EASYRSA_EXTRA_EXTS" | grep -q subjectAltName + + if [ $? -eq 0 ]; then + print "$(echo "$EASYRSA_EXTRA_EXTS" | grep subjectAltName | sed 's/^\s*subjectAltName\s*=\s*//')" + else + san=$( + "$EASYRSA_OPENSSL" "$format" -in "$path" -noout -text | + sed -n "/X509v3 Subject Alternative Name:/{n;s/ //g;s/IPAddress:/IP:/g;s/RegisteredID/RID/;p;}" + ) + + [ -n "$san" ] && print "$san" + fi +} + +# display cert DN info on a req/X509, passed by full pathname +display_dn() { + format="$1" path="$2" + print "$("$EASYRSA_OPENSSL" "$format" -in "$path" -noout -subject -nameopt multiline)" + san=$(display_san "$1" "$2") + if [ -n "$san" ]; then + print "" + print "X509v3 Subject Alternative Name:" + print " $san" + fi + +} # => display_dn() + +# generate default SAN from req/X509, passed by full pathname +default_server_san() { + path="$1" + cn=$( + easyrsa_openssl req -in "$path" -noout -subject -nameopt sep_multiline | + awk -F'=' '/^ *CN=/{print $2}' + ) + echo "$cn" | grep -E -q '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$' + #shellcheck disable=SC2181 + if [ $? -eq 0 ]; then + print "subjectAltName = IP:$cn" + else + print "subjectAltName = DNS:$cn" + fi +} # => default_server_san() + +# verify a file seems to be a valid req/X509 +verify_file() { + format="$1" + path="$2" + easyrsa_openssl "$format" -in "$path" -noout 2>/dev/null || return 1 + return 0 +} # => verify_file() + +# show-* command backend +# Prints req/cert details in a readable format +show() { + type="$1" + name="$2" + in_file="" + format="" + [ -n "$name" ] || die "\ +Missing expected filename_base argument. +Run easyrsa without commands for usage help." + shift 2 + + # opts support + opts="-${type}opt no_pubkey,no_sigdump" + while [ -n "$1" ]; do + case "$1" in + full) + opts="" + ;; + *) + warn "Ignoring unknown command option: '$1'" + ;; + esac + shift + done + + # Determine cert/req type + if [ "$type" = "cert" ]; then + verify_ca_init + in_file="$EASYRSA_PKI/issued/${name}.crt" + format="x509" + else + verify_pki_init + in_file="$EASYRSA_PKI/reqs/${name}.req" + format="req" + fi + + # Verify file exists and is of the correct type + [ -f "$in_file" ] || die "\ +No such $type file with a basename of '$name' is present. +Expected to find this file at: +$in_file" + verify_file $format "$in_file" || die "\ +This file is not a valid $type file: +$in_file" + + notice "\ +Showing $type details for '$name'. +This file is stored at: +$in_file +" + easyrsa_openssl $format -in "$in_file" -noout -text\ + -nameopt multiline $opts || die "\ +OpenSSL failure to process the input" +} # => show() + +# show-ca command backend +# Prints CA cert details in a readable format +show_ca() { + # opts support + opts="-certopt no_pubkey,no_sigdump" + while [ -n "$1" ]; do + case "$1" in + full) opts= ;; + *) warn "Ignoring unknown command option: '$1'" ;; + esac + shift + done + + verify_ca_init + in_file="$EASYRSA_PKI/ca.crt" + format="x509" + + # Verify file exists and is of the correct type + [ -f "$in_file" ] || die "\ +No such $type file with a basename of '$name' is present. +Expected to find this file at: +$in_file" + verify_file $format "$in_file" || die "\ +This file is not a valid $type file: +$in_file" + + notice "\ +Showing $type details for 'ca'. +This file is stored at: +$in_file +" + easyrsa_openssl $format -in "$in_file" -noout -text\ + -nameopt multiline $opts || die "\ +OpenSSL failure to process the input" +} # => show_ca() + +# vars setup +# Here sourcing of 'vars' if present occurs. If not present, defaults are used +# to support running without a sourced config format +vars_setup() { + # Try to locate a 'vars' file in order of location preference. + # If one is found, source it + vars= + + # set up program path + prog_file="$0" + prog_file2="$(which -- "$prog_file" 2>/dev/null)" && prog_file="$prog_file2" + prog_file2="$(readlink -f "$prog_file" 2>/dev/null)" && prog_file="$prog_file2" + prog_dir="${prog_file%/*}" + prog_vars="${prog_dir}/vars" + # set up PKI path + pki_vars="${EASYRSA_PKI:-$PWD/pki}/vars" + + # command-line path: + if [ ! -z "$EASYRSA_VARS_FILE" ]; then + if [ ! -f "$EASYRSA_VARS_FILE" ]; then + # If the --vars option does not point to a file, show helpful error. + die "The file '$EASYRSA_VARS_FILE' was not found." + fi + vars="$EASYRSA_VARS_FILE" + # PKI location, if present: + elif [ -f "$pki_vars" ]; then + vars="$pki_vars" + # EASYRSA, if defined: + elif [ -n "$EASYRSA" ] && [ -f "$EASYRSA/vars" ]; then + vars="$EASYRSA/vars" + # program location: + elif [ -f "$prog_vars" ]; then + vars="$prog_vars" + fi + + # If a vars file was located, source it + # If $EASYRSA_NO_VARS is defined (not blank) this is skipped + if [ -z "$EASYRSA_NO_VARS" ] && [ -n "$vars" ]; then + if grep -Eq 'EASYRSA_PASSIN|EASYRSA_PASSOUT' "$vars"; then + die "\ +Variable EASYRSA_PASSIN or EASYRSA_PASSOUT has been found in the configuration \ +file. Storing sensitive information in the configuration file is not \ +recommended - please remove it from there before continuing." + fi + #shellcheck disable=SC2034 + EASYRSA_CALLER=1 + # shellcheck disable=SC1090 + . "$vars" + notice "\ +Note: using Easy-RSA configuration from: $vars" + fi + + # Set defaults, preferring existing env-vars if present + set_var EASYRSA "$prog_dir" + set_var EASYRSA_OPENSSL openssl + set_var EASYRSA_PKI "$PWD/pki" + set_var EASYRSA_DN cn_only + set_var EASYRSA_REQ_COUNTRY "US" + set_var EASYRSA_REQ_PROVINCE "California" + set_var EASYRSA_REQ_CITY "San Francisco" + set_var EASYRSA_REQ_ORG "Copyleft Certificate Co" + set_var EASYRSA_REQ_EMAIL me@example.net + set_var EASYRSA_REQ_OU "My Organizational Unit" + set_var EASYRSA_ALGO rsa + set_var EASYRSA_KEY_SIZE 2048 + set_var EASYRSA_CURVE secp384r1 + set_var EASYRSA_EC_DIR "$EASYRSA_PKI/ecparams" + set_var EASYRSA_CA_EXPIRE 3650 + set_var EASYRSA_CERT_EXPIRE 825 # new default of 36 months + set_var EASYRSA_CERT_RENEW 30 + set_var EASYRSA_CRL_DAYS 180 + set_var EASYRSA_NS_SUPPORT no + set_var EASYRSA_NS_COMMENT "Easy-RSA (3.0.8) Generated Certificate" + set_var EASYRSA_TEMP_DIR "$EASYRSA_PKI" + set_var EASYRSA_REQ_CN ChangeMe + set_var EASYRSA_DIGEST sha256 + set_var EASYRSA_SSL_CONF "$EASYRSA_PKI/openssl-easyrsa.cnf" + set_var EASYRSA_SAFE_CONF "$EASYRSA_PKI/safessl-easyrsa.cnf" + set_var EASYRSA_KDC_REALM "CHANGEME.EXAMPLE.COM" + + # Same as above for the x509-types extensions dir + if [ -d "$EASYRSA_PKI/x509-types" ]; then + set_var EASYRSA_EXT_DIR "$EASYRSA_PKI/x509-types" + else + #TODO: This should be removed. Not really suitable for packaging. + set_var EASYRSA_EXT_DIR "$EASYRSA/x509-types" + fi + + # EASYRSA_ALGO_PARAMS must be set depending on selected algo + if [ "ec" = "$EASYRSA_ALGO" ]; then + EASYRSA_ALGO_PARAMS="$EASYRSA_EC_DIR/${EASYRSA_CURVE}.pem" + elif [ "rsa" = "$EASYRSA_ALGO" ]; then + EASYRSA_ALGO_PARAMS="${EASYRSA_KEY_SIZE}" + elif [ "ed" != "$EASYRSA_ALGO" ]; then + die "Alg '$EASYRSA_ALGO' is invalid: must be 'rsa', 'ec' or 'ed' " + fi + + # Assign value to $EASYRSA_TEMP_DIR_session and work around Windows mktemp bug when parent dir is missing + if [ -z "$EASYRSA_TEMP_DIR_session" ]; then + if [ -d "$EASYRSA_TEMP_DIR" ]; then + EASYRSA_TEMP_DIR_session="$(mktemp -du "$EASYRSA_TEMP_DIR/easy-rsa-$$.XXXXXX")" + else + # If the directory does not exist then we have not run init-pki + mkdir -p "$EASYRSA_TEMP_DIR" || die "Cannot create $EASYRSA_TEMP_DIR (permission?)" + EASYRSA_TEMP_DIR_session="$(mktemp -du "$EASYRSA_TEMP_DIR/easy-rsa-$$.XXXXXX")" + rm -rf "$EASYRSA_TEMP_DIR" + fi + fi + + # Setting OPENSSL_CONF prevents bogus warnings (especially useful on win32) + export OPENSSL_CONF="$EASYRSA_SAFE_CONF" + + # Upgrade to 306: Create $EASYRSA_SSL_CONF if it does not exist but only if $EASYRSA_PKI exists. + if [ ! -f "$EASYRSA_SSL_CONF" ] && [ -f "$EASYRSA/openssl-easyrsa.cnf" ] && [ -d "$EASYRSA_PKI" ]; + then + cp "$EASYRSA/openssl-easyrsa.cnf" "$EASYRSA_SSL_CONF" + easyrsa_openssl makesafeconf + fi + +} # vars_setup() + +# variable assignment by indirection when undefined; merely exports +# the variable when it is already defined (even if currently null) +# Sets $1 as the value contained in $2 and exports (may be blank) +set_var() { + var=$1 + shift + value="$*" + eval "export $var=\"\${$var-$value}\"" +} #=> set_var() + + +############################################################################ +# Upgrade v2 PKI to v3 PKI + +# You can report problems on the normal openvpn support channels: +# -------------------------------------------------------------------------- +# 1. The Openvpn Forum: https://forums.openvpn.net/viewforum.php?f=31 +# 2. The #easyrsa IRC channel at freenode +# 3. Info: https://community.openvpn.net/openvpn/wiki/easyrsa-upgrade +# -------------------------------------------------------------------------- +# + +up23_fail_upgrade () +{ + # Replace die() + unset EASYRSA_BATCH + notice " +============================================================================ +The update has failed but NOTHING has been lost. + +ERROR: $1 +---------------------------------------------------------------------------- + +Further info: +* https://community.openvpn.net/openvpn/wiki/easyrsa-upgrade#ersa-up23-fails + +Easyrsa3 upgrade FAILED +============================================================================ +" + exit 9 +} #=> up23_fail_upgrade () + +up23_verbose () +{ + [ "$VERBOSE" ] || return 0 + printf "%s\n" "$1" +} #=> up23_verbose () + +up23_verify_new_pki () +{ + # Fail now, before any changes are made + + up23_verbose "> Verify DEFAULT NEW PKI does not exist .." + EASYRSA_NEW_PKI="$EASYRSA/pki" + [ -d "$EASYRSA_NEW_PKI" ] \ + && up23_fail_upgrade "DEFAULT NEW PKI exists: $EASYRSA_NEW_PKI" + + up23_verbose "> Verify VERY-SAFE-PKI does not exist .." + EASYRSA_SAFE_PKI="$EASYRSA/VERY-SAFE-PKI" + [ -d "$EASYRSA_SAFE_PKI" ] \ + && up23_fail_upgrade "VERY-SAFE-PKI exists: $EASYRSA_SAFE_PKI" + + up23_verbose "> Verify openssl-easyrsa.cnf does exist .." + EASYRSA_SSL_CNFFILE="$EASYRSA/openssl-easyrsa.cnf" + [ -f "$EASYRSA_SSL_CNFFILE" ] \ + || up23_fail_upgrade "cannot find $EASYRSA_SSL_CNFFILE" + + up23_verbose "> Verify vars.example does exist .." + EASYRSA_VARSV3_EXMP="$EASYRSA/vars.example" + [ -f "$EASYRSA_VARSV3_EXMP" ] \ + || up23_fail_upgrade "cannot find $EASYRSA_VARSV3_EXMP" + + up23_verbose "> OK" + up23_verbose " Initial dirs & files are in a workable state." +} #=> up23_verify_new_pki () + +up23_verify_current_pki () +{ + up23_verbose "> Verify CURRENT PKI vars .." + + # This can probably be improved + EASYRSA_NO_REM="$(grep '^set ' "$EASYRSA_VER2_VARSFILE")" + + # This list may not be complete + # Not required: DH_KEY_SIZE PKCS11_MODULE_PATH PKCS11_PIN + for i in KEY_DIR KEY_SIZE KEY_COUNTRY KEY_PROVINCE \ + KEY_CITY KEY_ORG KEY_EMAIL KEY_CN KEY_NAME KEY_OU + do + # Effectively, source the v2 vars file + UNIQUE="set $i" + KEY_grep="$(printf "%s\n" "$EASYRSA_NO_REM" | grep "$UNIQUE")" + KEY_value="${KEY_grep##*=}" + set_var $i "$KEY_value" + done + + [ -d "$KEY_DIR" ] || up23_fail_upgrade "Cannot find CURRENT PKI KEY_DIR: $KEY_DIR" + + up23_verbose "> OK" + up23_verbose " Current CURRENT PKI vars uses PKI in: $KEY_DIR" +} #=> up23_verify_current_pki () + +up23_verify_current_ca () +{ + up23_verbose "> Find CA .." + # $KEY_DIR is assigned in up23_verify_current_pki () + [ -f "$KEY_DIR/ca.crt" ] \ + || up23_fail_upgrade "Cannot find current ca.crt: $KEY_DIR/ca.crt" + up23_verbose "> OK" + + # If CA is already verified then return + in_file="$KEY_DIR/ca.crt" + [ "$CURRENT_CA_IS_VERIFIED" = "$in_file" ] && return 0 + format="x509" + + # Current CA is unverified + # Extract the current CA details + CA_SUBJECT="$(easyrsa_openssl $format -in "$in_file" -subject -noout -nameopt multiline)" + + # Extract individual elements + CA_countryName="$(printf "%s\n" "$CA_SUBJECT" \ + | grep countryName | sed "s\`^.*=\ \`\`g")" + CA_stateOrProvinceName="$(printf "%s\n" "$CA_SUBJECT" \ + | grep stateOrProvinceName | sed "s\`^.*=\ \`\`g")" + CA_localityName="$(printf "%s\n" "$CA_SUBJECT" \ + | grep localityName | sed "s\`^.*=\ \`\`g")" + CA_organizationName="$(printf "%s\n" "$CA_SUBJECT" \ + | grep organizationName | sed "s\`^.*=\ \`\`g")" + CA_organizationalUnitName="$(printf "%s\n" "$CA_SUBJECT" \ + | grep organizationalUnitName | sed "s\`^.*=\ \`\`g")" + CA_emailAddress="$(printf "%s\n" "$CA_SUBJECT" \ + | grep emailAddress | sed "s\`^.*=\ \`\`g")" + + # Match the current CA elements to the vars file settings + CA_vars_match=1 + [ "$CA_countryName" = "$KEY_COUNTRY" ] || CA_vars_match=0 + [ "$CA_stateOrProvinceName" = "$KEY_PROVINCE" ] || CA_vars_match=0 + [ "$CA_localityName" = "$KEY_CITY" ] || CA_vars_match=0 + [ "$CA_organizationName" = "$KEY_ORG" ] || CA_vars_match=0 + [ "$CA_organizationalUnitName" = "$KEY_OU" ] || CA_vars_match=0 + [ "$CA_emailAddress" = "$KEY_EMAIL" ] || CA_vars_match=0 + + if [ "$CA_vars_match" -eq 1 ] + then + CURRENT_CA_IS_VERIFIED="partially" + else + up23_fail_upgrade "CA certificate does not match vars file settings" + fi + + opts="-certopt no_pubkey,no_sigdump" + if [ ! "$EASYRSA_BATCH" ] + then + up23_show_current_ca + elif [ "$VERBOSE" ] + then + up23_show_current_ca + fi + confirm "* Confirm CA shown above is correct: " "yes" \ + "Found current CA at: $KEY_DIR/ca.crt" + CURRENT_CA_IS_VERIFIED="$in_file" +} #=> up23_verify_current_ca () + +up23_show_current_ca () +{ + printf "%s\n" "-------------------------------------------------------------------------" + # $opts is always set here + # shellcheck disable=SC2086 + easyrsa_openssl $format -in "$in_file" -noout -text\ + -nameopt multiline $opts || die "\ + OpenSSL failure to process the input CA certificate: $in_file" + printf "%s\n" "-------------------------------------------------------------------------" +} #=> up23_show_current_ca () + +up23_backup_current_pki () +{ + up23_verbose "> Backup current PKI .." + + mkdir -p "$EASYRSA_SAFE_PKI" \ + || up23_fail_upgrade "Failed to create safe PKI dir: $EASYRSA_SAFE_PKI" + + cp -r "$KEY_DIR" "$EASYRSA_SAFE_PKI" \ + || up23_fail_upgrade "Failed to copy $KEY_DIR to $EASYRSA_SAFE_PKI" + + # EASYRSA_VER2_VARSFILE is either version 2 *nix ./vars or Win vars.bat + cp "$EASYRSA_VER2_VARSFILE" "$EASYRSA_SAFE_PKI" \ + || up23_fail_upgrade "Failed to copy $EASYRSA_VER2_VARSFILE to EASYRSA_SAFE_PKI" + + up23_verbose "> OK" + up23_verbose " Current PKI backup created in: $EASYRSA_SAFE_PKI" +} #=> up23_backup_current_pki () + +up23_create_new_pki () +{ + # Dirs: renewed and revoked are created when used. + up23_verbose "> Create NEW PKI .." + up23_verbose ">> Create NEW PKI dirs .." + for i in private reqs issued certs_by_serial + do + mkdir -p "$EASYRSA_PKI/$i" \ + || up23_fail_upgrade "Failed to Create NEW PKI dir: $EASYRSA_PKI/$i" + done + up23_verbose ">> OK" + + up23_verbose ">> Copy database to NEW PKI .." + # Failure for these is not optional + # Files ignored: index.txt.old serial.old + for i in index.txt serial ca.crt index.txt.attr + do + cp "$KEY_DIR/$i" "$EASYRSA_PKI" \ + || up23_fail_upgrade "Failed to copy $KEY_DIR/$i to $EASYRSA_PKI" + done + up23_verbose ">> OK" + + up23_verbose ">> Copy current PKI to NEW PKI .." + for i in "csr.reqs" "pem.certs_by_serial" "crt.issued" "key.private" \ + "p12.private" "p8.private" "p7b.issued" + do + FILE_EXT="${i%%.*}" + DEST_DIR="${i##*.}" + if ls "$KEY_DIR/"*".$FILE_EXT" > /dev/null 2>&1; then + cp "$KEY_DIR/"*".$FILE_EXT" "$EASYRSA_PKI/$DEST_DIR" \ + || up23_fail_upgrade "Failed to copy .$FILE_EXT" + else + up23_verbose " Note: No .$FILE_EXT files found" + fi + done + up23_verbose ">> OK" + up23_verbose "> OK" + + # Todo: CRL - Or generate a new CRL on completion + up23_verbose " New PKI created in: $EASYRSA_PKI" +} #=> up23_create_new_pki () + +up23_upgrade_ca () +{ + [ -d "$EASYRSA_PKI" ] || return 0 + up23_verbose "> Confirm that index.txt.attr exists and 'unique_subject = no'" + if [ -f "$EASYRSA_PKI/index.txt.attr" ] + then + if grep -q 'unique_subject = no' "$EASYRSA_PKI/index.txt.attr" + then + # If index.txt.attr exists and "unique_suject = no" then do nothing + return 0 + fi + else + # If index.txt.attr does not exists then do nothing + return 0 + fi + + # Otherwise this is required for all easyrsa v3 + #confirm "Set 'unique_subject = no' in index.txt.attr for your current CA: " \ + #"yes" "This version of easyrsa requires that 'unique_subject = no' is set correctly" + + printf "%s\n" "unique_subject = no" > "$EASYRSA_PKI/index.txt.attr" + up23_verbose "> OK" + up23_verbose " Upgraded index.txt.attr to v306+" +} #=> up23_upgrade_index_txt_attr () + +up23_create_openssl_cnf () +{ + up23_verbose "> OpenSSL config .." + EASYRSA_PKI_SSL_CNFFILE="$EASYRSA_PKI/openssl-easyrsa.cnf" + EASYRSA_PKI_SAFE_CNFFILE="$EASYRSA_PKI/safessl-easyrsa.cnf" + cp "$EASYRSA_SSL_CNFFILE" "$EASYRSA_PKI_SSL_CNFFILE" \ + || up23_fail_upgrade "create $EASYRSA_PKI_SSL_CNFFILE" + up23_verbose "> OK" + up23_verbose " New OpenSSL config file created in: $EASYRSA_PKI_SSL_CNFFILE" + + # Create $EASYRSA_PKI/safessl-easyrsa.cnf + easyrsa_openssl makesafeconf + if [ -f "$EASYRSA_PKI_SAFE_CNFFILE" ] + then + up23_verbose " New SafeSSL config file created in: $EASYRSA_PKI_SAFE_CNFFILE" + else + up23_verbose " FAILED to create New SafeSSL config file in: $EASYRSA_PKI_SAFE_CNFFILE" + fi +} #=> up23_create_openssl_cnf () + +up23_move_easyrsa2_programs () +{ + # These files may not exist here + up23_verbose "> Move easyrsa2 programs to SAFE PKI .." + for i in build-ca build-dh build-inter build-key build-key-pass \ + build-key-pkcs12 build-key-server build-req build-req-pass \ + clean-all inherit-inter list-crl pkitool revoke-full sign-req \ + whichopensslcnf build-ca-pass build-key-server-pass init-config \ + make-crl revoke-crt openssl-0.9.6.cnf openssl-0.9.8.cnf \ + openssl-1.0.0.cnf openssl.cnf README.txt index.txt.start \ + vars.bat.sample serial.start + do + # Although unlikely, both files could exist + # EG: ./build-ca and ./build-ca.bat + NIX_FILE="$EASYRSA/$i" + WIN_FILE="$EASYRSA/$i.bat" + if [ -f "$NIX_FILE" ] + then + cp "$NIX_FILE" "$EASYRSA_SAFE_PKI" \ + || up23_fail_upgrade "copy $NIX_FILE $EASYRSA_SAFE_PKI" + fi + + if [ -f "$WIN_FILE" ] + then + cp "$WIN_FILE" "$EASYRSA_SAFE_PKI" \ + || up23_fail_upgrade "copy $WIN_FILE $EASYRSA_SAFE_PKI" + fi + + if [ ! -f "$NIX_FILE" ] && [ ! -f "$WIN_FILE" ] + then + up23_verbose "File does not exist, ignoring: $i(.bat)" + fi + + # These files are not removed on TEST run + [ "$NOSAVE" -eq 1 ] && rm -f "$NIX_FILE" "$WIN_FILE" + done + + up23_verbose "> OK" + up23_verbose " Easyrsa2 programs successfully moved to: $EASYRSA_SAFE_PKI" +} #=> up23_move_easyrsa2_programs () + +up23_build_v3_vars () +{ + up23_verbose "> Build v3 vars file .." + + EASYRSA_EXT="easyrsa-upgrade-23" + EASYRSA_VARSV2_TMP="$EASYRSA/vars-v2.tmp.$EASYRSA_EXT" + rm -f "$EASYRSA_VARSV2_TMP" + EASYRSA_VARSV3_TMP="$EASYRSA/vars-v3.tmp.$EASYRSA_EXT" + rm -f "$EASYRSA_VARSV3_TMP" + EASYRSA_VARSV3_NEW="$EASYRSA/vars-v3.new.$EASYRSA_EXT" + rm -f "$EASYRSA_VARSV3_NEW" + EASYRSA_VARSV3_WRN="$EASYRSA/vars-v3.wrn.$EASYRSA_EXT" + rm -f "$EASYRSA_VARSV3_WRN" + + printf "%s\n" "\ +########################++++++++++######################### +### ### +### WARNING: THIS FILE WAS AUTOMATICALLY GENERATED ### +### ALL SETTINGS ARE AT THE END OF THE FILE ### +### ### +########################++++++++++######################### + +" > "$EASYRSA_VARSV3_WRN" || up23_fail_upgrade "Failed to create $EASYRSA_VARSV3_WRN" + + # Create vars v3 temp file from sourced vars v2 key variables + { + printf "%s\n" "set_var EASYRSA_KEY_SIZE $KEY_SIZE" + printf "%s\n" "set_var EASYRSA_REQ_COUNTRY \"$KEY_COUNTRY\"" + printf "%s\n" "set_var EASYRSA_REQ_PROVINCE \"$KEY_PROVINCE\"" + printf "%s\n" "set_var EASYRSA_REQ_CITY \"$KEY_CITY\"" + printf "%s\n" "set_var EASYRSA_REQ_ORG \"$KEY_ORG\"" + printf "%s\n" "set_var EASYRSA_REQ_EMAIL \"$KEY_EMAIL\"" + printf "%s\n" "set_var EASYRSA_REQ_OU \"$KEY_OU\"" + printf "%s\n" 'set_var EASYRSA_NS_SUPPORT "yes"' + printf "%s\n" 'set_var EASYRSA_DN "org"' + printf "%s\n" 'set_var EASYRSA_RAND_SN "no"' + printf "%s\n" "" + } > "$EASYRSA_VARSV3_TMP" \ + || up23_fail_upgrade "Failed to create $EASYRSA_VARSV3_TMP" + + # cat temp files into new v3 vars + cat "$EASYRSA_VARSV3_WRN" "$EASYRSA_VARSV3_EXMP" "$EASYRSA_VARSV3_TMP" \ + > "$EASYRSA_VARSV3_NEW" \ + || up23_fail_upgrade "Failed to create $EASYRSA_VARSV3_NEW" + + # This file must be created and restored at the end of TEST + # for the REAL update to to succeed + EASYRSA_VARS_LIVEBKP="$EASYRSA_TARGET_VARSFILE.livebackup" + cp "$EASYRSA_VER2_VARSFILE" "$EASYRSA_VARS_LIVEBKP" \ + || up23_fail_upgrade "Failed to create $EASYRSA_VARS_LIVEBKP" + rm -f "$EASYRSA_VER2_VARSFILE" + + # "$EASYRSA_TARGET_VARSFILE" is always $EASYRSA/vars + cp "$EASYRSA_VARSV3_NEW" "$EASYRSA_TARGET_VARSFILE" \ + || up23_fail_upgrade "copy $EASYRSA_VARSV3_NEW to $EASYRSA_TARGET_VARSFILE" + + # Delete temp files + rm -f "$EASYRSA_VARSV2_TMP" "$EASYRSA_VARSV3_TMP" \ + "$EASYRSA_VARSV3_NEW" "$EASYRSA_VARSV3_WRN" + + up23_verbose "> OK" + up23_verbose " New v3 vars file created in: $EASYRSA_TARGET_VARSFILE" +} #=> up23_build_v3_vars () + +up23_do_upgrade_23 () +{ + up23_verbose "============================================================================" + up23_verbose "Begin ** $1 ** upgrade process .." + up23_verbose "" + up23_verbose "Easyrsa upgrade version: $EASYRSA_UPGRADE_23" + up23_verbose "" + + up23_verify_new_pki + up23_verify_current_pki + up23_verify_current_ca + up23_backup_current_pki + up23_create_new_pki + up23_upgrade_ca + up23_move_easyrsa2_programs + up23_build_v3_vars + up23_create_openssl_cnf + + if [ "$NOSAVE" -eq 0 ] + then + # Must stay in this order + # New created dirs: EASYRSA_NEW_PKI and EASYRSA_SAFE_PKI + rm -rf "$EASYRSA_NEW_PKI" + rm -rf "$EASYRSA_SAFE_PKI" + # EASYRSA_TARGET_VARSFILE is always the new created v3 vars + # Need to know if this fails + rm "$EASYRSA_TARGET_VARSFILE" \ + || up23_fail_upgrade "remove new vars file: $EASYRSA_TARGET_VARSFILE" + # EASYRSA_VER2_VARSFILE is either v2 *nix ./vars or Win vars.bat + # Need this dance because v2 vars is same name as v3 vars above + cp "$EASYRSA_VARS_LIVEBKP" "$EASYRSA_VER2_VARSFILE" + fi + rm -f "$EASYRSA_VARS_LIVEBKP" +} #= up23_do_upgrade_23 () + +up23_manage_upgrade_23 () +{ + EASYRSA_UPGRADE_VERSION="v1.0a (2020/01/08)" + EASYRSA_UPGRADE_TYPE="$1" + EASYRSA_FOUND_VARS=0 + + # Verify all existing versions of vars/vars.bat + if [ -f "$vars" ] + then + if grep -q 'Complain if a user tries to do this:' "$vars" + then + EASYRSA_FOUND_VARS=1 + EASYRSA_VARS_IS_VER3=1 + fi + + # Easyrsa v3 does not use NOR allow use of `export`. + if grep -q 'export' "$vars" + then + EASYRSA_FOUND_VARS=1 + EASYRSA_VARS_IS_VER2=1 + EASYRSA_VER2_VARSFILE="$vars" + EASYRSA_TARGET_VARSFILE="$vars" + fi + fi + + if [ -f "$EASYRSA/vars.bat" ] + then + EASYRSA_FOUND_VARS=1 + EASYRSA_VARS_IS_WIN2=1 + EASYRSA_VER2_VARSFILE="$EASYRSA/vars.bat" + EASYRSA_TARGET_VARSFILE="$EASYRSA/vars" + fi + + if [ $EASYRSA_FOUND_VARS -ne 1 ]; + then + die echo "vars file not found" + fi + + # Only allow specific vars/vars.bat to exist + if [ "$EASYRSA_VARS_IS_VER3" ] && [ "$EASYRSA_VARS_IS_VER2" ] + then + die "Verify your current vars file, v3 cannot use 'export'." + fi + + if [ "$EASYRSA_VARS_IS_VER3" ] && [ "$EASYRSA_VARS_IS_WIN2" ] + then + die "Verify your current vars/vars.bat file, cannot have both." + fi + + if [ "$EASYRSA_VARS_IS_VER2" ] && [ "$EASYRSA_VARS_IS_WIN2" ] + then + die "Verify your current vars/vars.bat file, cannot have both." + fi + + # Die on invalid upgrade type or environment + if [ "$EASYRSA_UPGRADE_TYPE" = "ca" ] + then + if [ "$EASYRSA_VARS_IS_VER3" ] + then + # v3 ensure index.txt.attr "unique_subject = no" + up23_upgrade_ca + unset EASYRSA_BATCH + notice "Your CA is fully up to date." + return 0 + else + die "Only v3 PKI CA can be upgraded." + fi + fi + + if [ "$EASYRSA_UPGRADE_TYPE" = "pki" ] + then + if [ "$EASYRSA_VARS_IS_VER3" ] + then + unset EASYRSA_BATCH + notice "Your PKI is fully up to date." + return 0 + fi + else + die "upgrade type must be 'pki' or 'ca'." + fi + + # PKI is potentially suitable for upgrade + + warn " +========================================================================= + + * WARNING * + +Found settings from EasyRSA-v2 which are not compatible with EasyRSA-v3. +Before you can continue, EasyRSA must upgrade your settings and PKI. +* Found EASYRSA and vars file: + $EASYRSA + $EASYRSA_VER2_VARSFILE : + +Further info: +* https://community.openvpn.net/openvpn/wiki/easyrsa-upgrade + +Easyrsa upgrade version: $EASYRSA_UPGRADE_VERSION +========================================================================= +" + +# Test upgrade + + NOSAVE=0 + + confirm "* EasyRSA **TEST** upgrade (Changes will NOT be written): " "yes" " +This upgrade will TEST that the upgrade works BEFORE making any changes." + + up23_do_upgrade_23 "TEST" + + notice " +========================================================================= + + * NOTICE * + +EasyRSA upgrade **TEST** has successfully completed. +" +# Upgrade for REAL + + NOSAVE=1 + + confirm "* EasyRSA **REAL** upgrade (Changes WILL be written): " "yes" " +========================================================================= + + * WARNING * + +Run REAL upgrade: Answer yes (Once completed you will have a version 3 PKI) +Terminate upgrade: Answer no (No changes have been made to your current PKI) +" + + confirm "* Confirm **REAL** upgrade (Changes will be written): " "yes" " +========================================================================= + + * SECOND WARNING * + +This upgrade will permanently write changes to your PKI ! +(With full backup backout) +" + up23_do_upgrade_23 "REAL" + + notice " +========================================================================= + + * NOTICE * + +Your settings and PKI have been successfully upgraded to EasyRSA version3 + +A backup of your current PKI is here: + $EASYRSA_SAFE_PKI + + * IMPORTANT NOTICE * + +1. YOU MUST VERIFY THAT YOUR NEW ./vars FILE IS SETUP CORRECTLY +2. IF YOU ARE USING WINDOWS YOU MUST ENSURE THAT openssl IS CORRECTLY DEFINED + IN ./vars (example follows) + + # + # This sample is in Windows syntax -- edit it for your path if not using PATH: + # set_var EASYRSA_OPENSSL \"C:/Program Files/OpenSSL-Win32/bin/openssl.exe\" + # + # Alternate location (Note: Forward slash '/' is correct for Windpws): + # set_var EASYRSA_OPENSSL \"C:/Program Files/Openvpn/bin/openssl.exe\" + # + +3. Finally, you can verify that easyrsa works by using these two commands: + ./easyrsa show-ca (Verify that your CA is intact and correct) + ./easyrsa gen-crl ((re)-generate a CRL file) + +Further info: +* https://community.openvpn.net/openvpn/wiki/easyrsa-upgrade" + up23_verbose " + * UPGRADE COMPLETED SUCCESSFULLY * +" + +return 0 + +} # => up23_manage_upgrade_23 () + +print_version() +{ + cat < print_version () + + +######################################## +# Invocation entry point: + +NL=' +' + +# Be secure with a restrictive umask +[ -z "$EASYRSA_NO_UMASK" ] && umask 077 + +# Parse options +while :; do + # Separate option from value: + opt="${1%%=*}" + val="${1#*=}" + empty_ok="" # Empty values are not allowed unless excepted + + case "$opt" in + --days) + export EASYRSA_CERT_EXPIRE="$val" + export EASYRSA_CA_EXPIRE="$val" + export EASYRSA_CRL_DAYS="$val" + ;; + --pki-dir) + export EASYRSA_PKI="$val" ;; + --use-algo) + export EASYRSA_ALGO="$val" ;; + --keysize) + export EASYRSA_KEY_SIZE="$val" ;; + --curve) + export EASYRSA_CURVE="$val" ;; + --dn-mode) + export EASYRSA_DN="$val" ;; + --req-cn) + export EASYRSA_REQ_CN="$val" ;; + --digest) + export EASYRSA_DIGEST="$val" ;; + --req-c) + empty_ok=1 + export EASYRSA_REQ_COUNTRY="$val" ;; + --req-st) + empty_ok=1 + export EASYRSA_REQ_PROVINCE="$val" ;; + --req-city) + empty_ok=1 + export EASYRSA_REQ_CITY="$val" ;; + --req-org) + empty_ok=1 + export EASYRSA_REQ_ORG="$val" ;; + --req-email) + empty_ok=1 + export EASYRSA_REQ_EMAIL="$val" ;; + --req-ou) + empty_ok=1 + export EASYRSA_REQ_OU="$val" ;; + --ns-cert) + export EASYRSA_NS_SUPPORT="$val" ;; + --ns-comment) + empty_ok=1 + export EASYRSA_NS_COMMENT="$val" ;; + --batch) + empty_ok=1 + export EASYRSA_BATCH=1 ;; + --passin) + export EASYRSA_PASSIN="$val";; + --passout) + export EASYRSA_PASSOUT="$val";; + --subca-len) + export EASYRSA_SUBCA_LEN="$val" ;; + --vars) + export EASYRSA_VARS_FILE="$val" ;; + --copy-ext) + empty_ok=1 + export EASYRSA_CP_EXT=1 ;; + --subject-alt-name) + export EASYRSA_EXTRA_EXTS="\ +$EASYRSA_EXTRA_EXTS +subjectAltName = $val" ;; + --version) + print_version + ;; + *) + break ;; + esac + + # fatal error when no value was provided + if [ ! $empty_ok ] && { [ "$val" = "$1" ] || [ -z "$val" ]; }; then + die "Missing value to option: $opt" + fi + + shift +done + +# Intelligent env-var detection and auto-loading: +vars_setup + +# Register cleanup on EXIT +trap "cleanup" EXIT +# When SIGHUP, SIGINT, SIGQUIT, SIGABRT and SIGTERM, +# explicitly exit to signal EXIT (non-bash shells) +trap "exit 1" 1 +trap "exit 2" 2 +trap "exit 3" 3 +trap "exit 6" 6 +trap "exit 14" 15 + +# Upgrade: EasyRSA v2.x to EasyRSA v3.x +# Upgrade: EasyRSA < v3.0.6 to v3.0.6+ +#up23_manage_upgrade_23 + +# determine how we were called, then hand off to the function responsible +cmd="$1" +[ -n "$1" ] && shift # scrape off command +case "$cmd" in + init-pki|clean-all) + init_pki "$@" + ;; + build-ca) + build_ca "$@" + ;; + gen-dh) + gen_dh + ;; + gen-req) + gen_req "$@" + ;; + sign|sign-req) + sign_req "$@" + ;; + build-client-full) + build_full client "$@" + ;; + build-server-full) + build_full server "$@" + ;; + build-serverClient-full) + build_full serverClient "$@" + ;; + gen-crl) + gen_crl + ;; + revoke) + revoke "$@" + ;; + renew) + renew "$@" + ;; + import-req) + import_req "$@" + ;; + export-p12) + export_pkcs p12 "$@" + ;; + export-p7) + export_pkcs p7 "$@" + ;; + export-p8) + export_pkcs p8 "$@" + ;; + set-rsa-pass) + set_pass rsa "$@" + ;; + set-ec-pass) + set_pass ec "$@" + ;; + update-db) + update_db + ;; + show-req) + show req "$@" + ;; + show-cert) + show cert "$@" + ;; + show-ca) + show_ca "$@" + ;; + upgrade) + up23_manage_upgrade_23 "$@" + ;; + ""|help|-h|--help|--usage) + cmd_help "$1" + exit 0 + ;; + version) + print_version + ;; + *) + die "Unknown command '$cmd'. Run without commands for usage help." + ;; +esac + +# vim: ft=sh nu ai sw=8 ts=8 noet diff --git a/deploy/data/linux/client/bin/openvpn/openssl-easyrsa.cnf b/deploy/data/linux/client/bin/openvpn/openssl-easyrsa.cnf new file mode 100644 index 00000000..5c4fc79e --- /dev/null +++ b/deploy/data/linux/client/bin/openvpn/openssl-easyrsa.cnf @@ -0,0 +1,138 @@ +# For use with Easy-RSA 3.0+ and OpenSSL or LibreSSL + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +#################################################################### +[ CA_default ] + +dir = $ENV::EASYRSA_PKI # Where everything is kept +certs = $dir # Where the issued certs are kept +crl_dir = $dir # Where the issued crl are kept +database = $dir/index.txt # database index file. +new_certs_dir = $dir/certs_by_serial # default place for new certs. + +certificate = $dir/ca.crt # The CA certificate +serial = $dir/serial # The current serial number +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/ca.key # The private key +RANDFILE = $dir/.rand # private random number file + +x509_extensions = basic_exts # The extensions to add to the cert + +# This allows a V2 CRL. Ancient browsers don't like it, but anything Easy-RSA +# is designed for will. In return, we get the Issuer attached to CRLs. +crl_extensions = crl_ext + +default_days = $ENV::EASYRSA_CERT_EXPIRE # how long to certify for +default_crl_days= $ENV::EASYRSA_CRL_DAYS # how long before next CRL +default_md = $ENV::EASYRSA_DIGEST # use public key default MD +preserve = no # keep passed DN ordering + +# This allows to renew certificates which have not been revoked +unique_subject = no + +# A few different ways of specifying how similar the request should look +# For type CA, the listed attributes must be the same, and the optional +# and supplied fields are just that :-) +policy = policy_anything + +# For the 'anything' policy, which defines allowed DN fields +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +name = optional +emailAddress = optional + +#################################################################### +# Easy-RSA request handling +# We key off $DN_MODE to determine how to format the DN +[ req ] +default_bits = $ENV::EASYRSA_KEY_SIZE +default_keyfile = privkey.pem +default_md = $ENV::EASYRSA_DIGEST +distinguished_name = $ENV::EASYRSA_DN +x509_extensions = easyrsa_ca # The extensions to add to the self signed cert + +# A placeholder to handle the $EXTRA_EXTS feature: +#%EXTRA_EXTS% # Do NOT remove or change this line as $EXTRA_EXTS support requires it + +#################################################################### +# Easy-RSA DN (Subject) handling + +# Easy-RSA DN for cn_only support: +[ cn_only ] +commonName = Common Name (eg: your user, host, or server name) +commonName_max = 64 +commonName_default = $ENV::EASYRSA_REQ_CN + +# Easy-RSA DN for org support: +[ org ] +countryName = Country Name (2 letter code) +countryName_default = $ENV::EASYRSA_REQ_COUNTRY +countryName_min = 2 +countryName_max = 2 + +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = $ENV::EASYRSA_REQ_PROVINCE + +localityName = Locality Name (eg, city) +localityName_default = $ENV::EASYRSA_REQ_CITY + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = $ENV::EASYRSA_REQ_ORG + +organizationalUnitName = Organizational Unit Name (eg, section) +organizationalUnitName_default = $ENV::EASYRSA_REQ_OU + +commonName = Common Name (eg: your user, host, or server name) +commonName_max = 64 +commonName_default = $ENV::EASYRSA_REQ_CN + +emailAddress = Email Address +emailAddress_default = $ENV::EASYRSA_REQ_EMAIL +emailAddress_max = 64 + +#################################################################### +# Easy-RSA cert extension handling + +# This section is effectively unused as the main script sets extensions +# dynamically. This core section is left to support the odd usecase where +# a user calls openssl directly. +[ basic_exts ] +basicConstraints = CA:FALSE +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer:always + +# The Easy-RSA CA extensions +[ easyrsa_ca ] + +# PKIX recommendations: + +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer:always + +# This could be marked critical, but it's nice to support reading by any +# broken clients who attempt to do so. +basicConstraints = CA:true + +# Limit key usage to CA tasks. If you really want to use the generated pair as +# a self-signed cert, comment this out. +keyUsage = cRLSign, keyCertSign + +# nsCertType omitted by default. Let's try to let the deprecated stuff die. +# nsCertType = sslCA + +# CRL extensions. +[ crl_ext ] + +# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. + +# issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always,issuer:always + diff --git a/deploy/data/linux/client/bin/qt.conf b/deploy/data/linux/client/bin/qt.conf new file mode 100644 index 00000000..b149771e --- /dev/null +++ b/deploy/data/linux/client/bin/qt.conf @@ -0,0 +1,7 @@ +# Generated by linuxdeployqt +# https://github.com/probonopd/linuxdeployqt/ +[Paths] +Prefix = ../ +Plugins = plugins +Imports = qml +Qml2Imports = qml diff --git a/deploy/data/linux/client/bin/update-resolv-conf.sh b/deploy/data/linux/client/bin/update-resolv-conf.sh new file mode 100644 index 00000000..16622a1d --- /dev/null +++ b/deploy/data/linux/client/bin/update-resolv-conf.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# +# Parses DHCP options from openvpn to update resolv.conf +# To use set as 'up' and 'down' script in your openvpn *.conf: +# up /etc/openvpn/update-resolv-conf +# down /etc/openvpn/update-resolv-conf +# +# Used snippets of resolvconf script by Thomas Hood +# and Chris Hanson +# Licensed under the GNU GPL. See /usr/share/common-licenses/GPL. +# 07/2013 colin@daedrum.net Fixed intet name +# 05/2006 chlauber@bnc.ch +# +# Example envs set from openvpn: +# foreign_option_1='dhcp-option DNS 193.43.27.132' +# foreign_option_2='dhcp-option DNS 193.43.27.133' +# foreign_option_3='dhcp-option DOMAIN be.bnc.ch' +# foreign_option_4='dhcp-option DOMAIN-SEARCH bnc.local' + +## The 'type' builtins will look for file in $PATH variable, so we set the +## PATH below. You might need to directly set the path to 'resolvconf' +## manually if it still doesn't work, i.e. +## RESOLVCONF=/usr/sbin/resolvconf +export PATH=$PATH:/sbin:/usr/sbin:/bin:/usr/bin +RESOLVCONF=$(type -p resolvconf) + +case $script_type in + +up) + for optionname in ${!foreign_option_*} ; do + option="${!optionname}" + echo $option + part1=$(echo "$option" | cut -d " " -f 1) + if [ "$part1" == "dhcp-option" ] ; then + part2=$(echo "$option" | cut -d " " -f 2) + part3=$(echo "$option" | cut -d " " -f 3) + if [ "$part2" == "DNS" ] ; then + IF_DNS_NAMESERVERS="$IF_DNS_NAMESERVERS $part3" + fi + if [[ "$part2" == "DOMAIN" || "$part2" == "DOMAIN-SEARCH" ]] ; then + IF_DNS_SEARCH="$IF_DNS_SEARCH $part3" + fi + fi + done + R="" + if [ "$IF_DNS_SEARCH" ]; then + R="search " + for DS in $IF_DNS_SEARCH ; do + R="${R} $DS" + done + R="${R} +" + fi + + for NS in $IF_DNS_NAMESERVERS ; do + R="${R}nameserver $NS +" + done + #echo -n "$R" | $RESOLVCONF -x -p -a "${dev}" + echo -n "$R" | $RESOLVCONF -x -a "${dev}.inet" + ;; +down) + $RESOLVCONF -d "${dev}.inet" + ;; +esac + +# Workaround / jm@epiclabs.io +# force exit with no errors. Due to an apparent conflict with the Network Manager +# $RESOLVCONF sometimes exits with error code 6 even though it has performed the +# action correctly and OpenVPN shuts down. +exit 0 diff --git a/deploy/data/linux/client/share/applications/AmneziaVPN_build.desktop b/deploy/data/linux/client/share/applications/AmneziaVPN_build.desktop new file mode 100644 index 00000000..65b43491 --- /dev/null +++ b/deploy/data/linux/client/share/applications/AmneziaVPN_build.desktop @@ -0,0 +1,8 @@ +#!/usr/bin/env xdg-open +[Desktop Entry] +Type=Application +Name=AmneziaVPN client +Comment=AmneziaVPN client +Exec=AmneziaVPN +Icon=AmneziaVPN_Logo.png +Categories=VPN diff --git a/deploy/data/linux/client/share/icons/AmneziaVPN_Logo.png b/deploy/data/linux/client/share/icons/AmneziaVPN_Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0e281afb9b3daa1307c9756ebc38478d67efd262 GIT binary patch literal 56043 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 literal 0 HcmV?d00001 diff --git a/deploy/data/linux/post_install.sh b/deploy/data/linux/post_install.sh new file mode 100644 index 00000000..4d189432 --- /dev/null +++ b/deploy/data/linux/post_install.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +APP_NAME=AmneziaVPN +LOG_FOLDER=/var/log/$APP_NAME +LOG_FILE="$LOG_FOLDER/post-install.log" +APP_PATH=/opt/$APP_NAME + +if ! test -f $LOG_FOLDER; then + sudo mkdir $LOG_FOLDER + echo "AmneziaVPN log dir created at /var/log/" +fi + +if ! test -f $LOG_FILE; then + touch $LOG_FILE + echo "AmneziaVPN log file created at /var/log/AmneziaVPN/post-install.log" +fi + +date > $LOG_FILE +echo "Script started" >> $LOG_FILE +sudo killall -9 $APP_NAME 2>> $LOG_FILE + +if sudo systemctl is-active --quiet $APP_NAME; then + sudo systemctl stop $APP_NAME >> $LOG_FILE + sudo systemctl disable $APP_NAME >> $LOG_FILE + sudo rm -rf /etc/systemd/system/$APP_NAME.service >> $LOG_FILE +fi + +sudo cp $APP_PATH/service/$APP_NAME.service /etc/systemd/system/ >> $LOG_FILE + +sudo ln -s $APP_PATH/client/lib/* /usr/lib/ >> $LOG_FILE + +sudo systemctl start $APP_NAME >> $LOG_FILE +sudo systemctl enable $APP_NAME >> $LOG_FILE +sudo ln -s $APP_PATH/client/bin/$APP_NAME /usr/sbin/ >> $LOG_FILE + + +echo "user desktop creation loop started" >> $LOG_FILE +getent passwd {1000..6000} | while IFS=: read -r name password uid gid gecos home shell; do + echo "name: $name" + if ! test -f /home/$name/.icons; then + mkdir /home/$name/.icons/ >> $LOG_FILE + fi + + cp -f $APP_PATH/client/share/icons/AmneziaVPN_Logo.png /home/$name/.icons/ >> $LOG_FILE + cp $APP_PATH/client/$APP_NAME.desktop /home/$name/Desktop/ >> $LOG_FILE + + sudo chown $name:$name /home/$name/.local/share/gvfs-metadata/home* >> $LOG_FILE + sudo -u $name dbus-launch gio set /home/$name/Desktop/AmneziaVPN.desktop "metadata::trusted" yes >> $LOG_FILE + sudo chown $name:$name /home/$name/Desktop/AmneziaVPN.desktop >> $LOG_FILE +done +echo "user desktop creation loop ended" >> $LOG_FILE + +date >> $LOG_FILE +echo "Service status:" >> $LOG_FILE +sudo systemctl status $APP_NAME >> $LOG_FILE +date >> $LOG_FILE +echo "Script finished" >> $LOG_FILE +exit 0 diff --git a/deploy/data/linux/post_uninstall.sh b/deploy/data/linux/post_uninstall.sh new file mode 100644 index 00000000..34b77b69 --- /dev/null +++ b/deploy/data/linux/post_uninstall.sh @@ -0,0 +1,74 @@ +#!/bin/bash + +APP_NAME=AmneziaVPN +LOG_FOLDER=/var/log/$APP_NAME +LOG_FILE="$LOG_FOLDER/post-uninstall.log" +APP_PATH=/opt/$APP_NAME + +if ! test -f $LOG_FILE; then + touch $LOG_FILE +fi + +date >> $LOG_FILE +echo "Uninstall Script started" >> $LOG_FILE +sudo killall -9 $APP_NAME 2>> $LOG_FILE + +ls /opt/AmneziaVPN/client/lib/* | while IFS=: read -r dir; do + sudo unlink $dir >> $LOG_FILE +done + +if sudo systemctl is-active --quiet $APP_NAME; then + sudo systemctl stop $APP_NAME >> $LOG_FILE +fi + +if sudo systemctl is-enabled --quiet $APP_NAME; then + sudo systemctl disable $APP_NAME >> $LOG_FILE +fi + +if test -f /etc/systemd/system/$APP_NAME.service; then + sudo rm -rf /etc/systemd/system/$APP_NAME.service >> $LOG_FILE +fi + +if test -f /usr/bin/$APP_NAME; then + sudo rm -rf /usr/sbin/$APP_NAME >> $LOG_FILE +fi + +if test -f $APP_PATH; then + sudo rm -rf $APP_PATH >> $LOG_FILE +fi + +if test -f /usr/sbin/$APP_NAME; then + sudo rm -rf /usr/sbin/$APP_NAME >> $LOG_FILE +fi + +getent passwd {1000..6000} | while IFS=: read -r name password uid gid gecos home shell; do + if test -f /home/$name/Desktop/$APP_NAME\ client.desktop; then + sudo rm -rf /home/$name/Desktop/$APP_NAME\ client.desktop >> $LOG_FILE + fi + + if test -f /home/$name/Desktop/$APP_NAME.desktop; then + sudo rm -rf /home/$name/Desktop/$APP_NAME.desktop >> $LOG_FILE + fi + + if test -f /home/$name/.config/$APP_NAME.ORG; then + sudo rm -rf /home/$name/.config/$APP_NAME.ORG >> $LOG_FILE + fi + + if test -f /home/$name/.local/share/$APP_NAME.ORG; then + sudo rm -rf /home/$name/.local/share/$APP_NAME.ORG >> $LOG_FILE + fi + + if test -f /home/$name/.local/share/$APP_NAME; then + sudo rm -rf /home/$name/.local/share/$APP_NAME >> $LOG_FILE + fi + + if test -f /home/$name/.icons/AmneziaVPN_Logo.png; then + sudo rm -rf /home/$name/.icons/AmneziaVPN_Logo.png >> $LOG_FILE + fi +done + +date >> $LOG_FILE +echo "Service after uninstall status:" >> $LOG_FILE +sudo systemctl status $APP_NAME >> $LOG_FILE +date >> $LOG_FILE +echo "Script finished" >> $LOG_FILE diff --git a/deploy/data/linux/service/AmneziaVPN.service b/deploy/data/linux/service/AmneziaVPN.service new file mode 100644 index 00000000..5050694f --- /dev/null +++ b/deploy/data/linux/service/AmneziaVPN.service @@ -0,0 +1,13 @@ +[Unit] +Description=AmneziaVPN Service +After=network.target +StartLimitIntervalSec=0 + +[Service] +Type=simple +Restart=always +RestartSec=1 +ExecStart=/opt/AmneziaVPN/service/bin/AmneziaVPN-service + +[Install] +WantedBy=multi-user.target diff --git a/deploy/data/linux/service/share/applications/AmneziaVPN_build.desktop b/deploy/data/linux/service/share/applications/AmneziaVPN_build.desktop new file mode 100644 index 00000000..a4336e6b --- /dev/null +++ b/deploy/data/linux/service/share/applications/AmneziaVPN_build.desktop @@ -0,0 +1,7 @@ +#!/usr/bin/env xdg-open +[Desktop Entry] +Type=Application +Name=AmneziaVPN service +Comment=AmneziaVPN service +Exec=AmneziaVPN-service +Categories=VPN diff --git a/deploy/installer/config/controlscript.js b/deploy/installer/config/controlscript.js index 90b6f7d3..9be76c15 100644 --- a/deploy/installer/config/controlscript.js +++ b/deploy/installer/config/controlscript.js @@ -25,6 +25,8 @@ function appInstalled() appInstalledUninstallerPath_x86 = installer.value("RootDir") + "Program Files (x86)/AmneziaVPN/maintenancetool.exe"; } else if (runningOnMacOS()){ appInstalledUninstallerPath = "/Applications/" + appName() + ".app/maintenancetool.app/Contents/MacOS/maintenancetool"; + } else if (runningOnLinux()){ + allInstalledUninstallerPath = "/opt/" + appName(); } return installer.fileExists(appInstalledUninstallerPath) || installer.fileExists(appInstalledUninstallerPath_x86); @@ -45,6 +47,11 @@ function runningOnMacOS() return (installer.value("os") === "mac"); } +function runningOnLinux() +{ + return (installer.value("os") === "linux"); +} + function sleep(miliseconds) { var currentTime = new Date().getTime(); while (currentTime + miliseconds >= new Date().getTime()) {} diff --git a/deploy/installer/config/linux.xml b/deploy/installer/config/linux.xml new file mode 100644 index 00000000..4a0038ce --- /dev/null +++ b/deploy/installer/config/linux.xml @@ -0,0 +1,27 @@ + + + AmneziaVPN + 1.6.0.0 + AmneziaVPN + AmneziaVPN + AmneziaVPN + @ApplicationsDir@/AmneziaVPN + 600 + 380 + Modern + true + true + false + controlscript.js + false + true + false + true + + + https://amneziavpn.org/updates/linux + true + AmneziaVPN - repository for Linux + + + diff --git a/deploy/installer/packages/org.amneziavpn.package/meta/componentscript.js b/deploy/installer/packages/org.amneziavpn.package/meta/componentscript.js index 289e323c..35dc7bf4 100644 --- a/deploy/installer/packages/org.amneziavpn.package/meta/componentscript.js +++ b/deploy/installer/packages/org.amneziavpn.package/meta/componentscript.js @@ -28,6 +28,11 @@ function runningOnMacOS() return (systemInfo.kernelType === "darwin"); } +function runningOnLinux() +{ + return (systemInfo.kernelType === "linux"); +} + function vcRuntimeIsInstalled() { return (installer.findPath("msvcp140.dll", [installer.value("RootDir")+ "\\Windows\\System32\\"]).length !== 0) @@ -86,6 +91,8 @@ Component.prototype.createOperations = function() } else if (runningOnMacOS()) { component.addElevatedOperation("Execute", "@TargetDir@/post_install.sh", "UNDOEXECUTE", "@TargetDir@/post_uninstall.sh"); + } else if (runningOnLinux()) { + component.addElevatedOperation("Execute", "bash", "@TargetDir@/post_install.sh", "UNDOEXECUTE", "bash", "@TargetDir@/post_uninstall.sh"); } } @@ -112,7 +119,9 @@ Component.prototype.installationFinished = function() } else if (runningOnMacOS()) { command = "/Applications/" + appName() + ".app/Contents/MacOS/" + appName(); - } + } else if (runningOnLinux()) { + command = "@TargetDir@/client/" + appName(); + } installer.dropAdminRights() diff --git a/service/server/router.cpp b/service/server/router.cpp index 97387900..3890fcb3 100644 --- a/service/server/router.cpp +++ b/service/server/router.cpp @@ -4,6 +4,8 @@ #include "router_win.h" #elif defined (Q_OS_MAC) #include "router_mac.h" +#elif defined Q_OS_LINUX +#include "router_linux.h" #endif @@ -13,6 +15,8 @@ int Router::routeAddList(const QString &gw, const QStringList &ips) return RouterWin::Instance().routeAddList(gw, ips); #elif defined (Q_OS_MAC) return RouterMac::Instance().routeAddList(gw, ips); +#elif defined Q_OS_LINUX + return RouterLinux::Instance().routeAddList(gw, ips); #endif } @@ -22,6 +26,8 @@ bool Router::clearSavedRoutes() return RouterWin::Instance().clearSavedRoutes(); #elif defined (Q_OS_MAC) return RouterMac::Instance().clearSavedRoutes(); +#elif defined Q_OS_LINUX + return RouterLinux::Instance().clearSavedRoutes(); #endif } @@ -31,6 +37,8 @@ int Router::routeDeleteList(const QString &gw, const QStringList &ips) return RouterWin::Instance().routeDeleteList(gw, ips); #elif defined (Q_OS_MAC) return RouterMac::Instance().routeDeleteList(gw, ips); +#elif defined Q_OS_LINUX + return RouterLinux::Instance().routeDeleteList(gw, ips); #endif } @@ -40,6 +48,8 @@ void Router::flushDns() RouterWin::Instance().flushDns(); #elif defined (Q_OS_MAC) RouterMac::Instance().flushDns(); +#elif defined Q_OS_LINUX + RouterLinux::Instance().flushDns(); #endif } diff --git a/service/server/router_linux.cpp b/service/server/router_linux.cpp new file mode 100644 index 00000000..0ec6250f --- /dev/null +++ b/service/server/router_linux.cpp @@ -0,0 +1,166 @@ +#include "router_linux.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +RouterLinux &RouterLinux::Instance() +{ + static RouterLinux s; + return s; +} + +bool RouterLinux::routeAdd(const QString &ipWithSubnet, const QString &gw, const int &sock) +{ + QString ip = Utils::ipAddressFromIpWithSubnet(ipWithSubnet); + QString mask = Utils::netMaskFromIpWithSubnet(ipWithSubnet); + + if (!Utils::checkIPv4Format(ip) || !Utils::checkIPv4Format(gw)) { + qCritical().noquote() << "Critical, trying to add invalid route: " << ip << gw; + return false; + } + + struct rtentry route; + memset(&route, 0, sizeof( route )); + + // set gateway + ((struct sockaddr_in *)&route.rt_gateway)->sin_family = AF_INET; + ((struct sockaddr_in *)&route.rt_gateway)->sin_addr.s_addr = inet_addr(gw.toStdString().c_str()); + ((struct sockaddr_in *)&route.rt_gateway)->sin_port = 0; + // set host rejecting + ((struct sockaddr_in *)&route.rt_dst)->sin_family = AF_INET; + ((struct sockaddr_in *)&route.rt_dst)->sin_addr.s_addr = inet_addr(ip.toStdString().c_str()); + ((struct sockaddr_in *)&route.rt_dst)->sin_port = 0; + // set mask + ((struct sockaddr_in *)&route.rt_genmask)->sin_family = AF_INET; + ((struct sockaddr_in *)&route.rt_genmask)->sin_addr.s_addr = inet_addr(mask.toStdString().c_str()); + ((struct sockaddr_in *)&route.rt_genmask)->sin_port = 0; + + route.rt_flags = RTF_UP | RTF_GATEWAY; + route.rt_metric = 0; + //route.rt_dev = "ens33"; + + if (int err = ioctl(sock, SIOCADDRT, &route) < 0) + { + qDebug().noquote() << "route add error: gw " + << ((struct sockaddr_in *)&route.rt_gateway)->sin_addr.s_addr + << " ip " << ((struct sockaddr_in *)&route.rt_dst)->sin_addr.s_addr + << " mask " << ((struct sockaddr_in *)&route.rt_genmask)->sin_addr.s_addr << " " << err; + return false; + } + return true; +} + +int RouterLinux::routeAddList(const QString &gw, const QStringList &ips) +{ + int temp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + int cnt = 0; + for (const QString &ip: ips) { + if (routeAdd(ip, gw, temp_sock)) cnt++; + } + close(temp_sock); + return cnt; +} + +bool RouterLinux::clearSavedRoutes() +{ + // No need to delete routes after iface down + return true; + +// int cnt = 0; +// for (const QString &ip: m_addedRoutes) { +// if (routeDelete(ip)) cnt++; +// } + // return (cnt == m_addedRoutes.count()); +} + +bool RouterLinux::routeDelete(const QString &ipWithSubnet, const QString &gw, const int &sock) +{ + QString ip = Utils::ipAddressFromIpWithSubnet(ipWithSubnet); + QString mask = Utils::netMaskFromIpWithSubnet(ipWithSubnet); + + if (!Utils::checkIPv4Format(ip) || !Utils::checkIPv4Format(gw)) { + qCritical().noquote() << "Critical, trying to remove invalid route: " << ip << gw; + return false; + } + + if (ip == "0.0.0.0") { + qDebug().noquote() << "Warning, trying to remove default route, skipping: " << ip << gw; + return true; + } + + struct rtentry route; + memset(&route, 0, sizeof( route )); + + // set gateway + ((struct sockaddr_in *)&route.rt_gateway)->sin_family = AF_INET; + ((struct sockaddr_in *)&route.rt_gateway)->sin_addr.s_addr = inet_addr(gw.toStdString().c_str()); + ((struct sockaddr_in *)&route.rt_gateway)->sin_port = 0; + // set host rejecting + ((struct sockaddr_in *)&route.rt_dst)->sin_family = AF_INET; + ((struct sockaddr_in *)&route.rt_dst)->sin_addr.s_addr = inet_addr(ip.toStdString().c_str()); + ((struct sockaddr_in *)&route.rt_dst)->sin_port = 0; + // set mask + ((struct sockaddr_in *)&route.rt_genmask)->sin_family = AF_INET; + ((struct sockaddr_in *)&route.rt_genmask)->sin_addr.s_addr = inet_addr(mask.toStdString().c_str()); + ((struct sockaddr_in *)&route.rt_genmask)->sin_port = 0; + + route.rt_flags = RTF_UP | RTF_GATEWAY; + route.rt_metric = 0; + //route.rt_dev = "ens33"; + + if (ioctl(sock, SIOCDELRT, &route) < 0) + { + qDebug().noquote() << "route delete error: gw " << gw << " ip " << ip << " mask " << mask; + return false; + } + return true; +} + +bool RouterLinux::routeDeleteList(const QString &gw, const QStringList &ips) +{ + int temp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + int cnt = 0; + for (const QString &ip: ips) { + if (routeDelete(ip, gw, temp_sock)) cnt++; + } + close(temp_sock); + return cnt; +} + +void RouterLinux::flushDns() +{ + QProcess p; + p.setProcessChannelMode(QProcess::MergedChannels); + + //check what the dns manager use + if (QFileInfo::exists("/usr/bin/nscd") + || QFileInfo::exists("/usr/sbin/nscd") + || QFileInfo::exists("/usr/lib/systemd/system/nscd.service")) + { + p.start("systemctl restart nscd"); + } + else + { + p.start("systemctl restart systemd-resolved"); + } + + p.waitForFinished(); + QByteArray output(p.readAll()); + if (output.isEmpty()) + qDebug().noquote() << "Flush dns completed"; + else + qDebug().noquote() << "OUTPUT systemctl restart nscd/systemd-resolved: " + output; +} diff --git a/service/server/router_linux.h b/service/server/router_linux.h new file mode 100644 index 00000000..ddd6e28f --- /dev/null +++ b/service/server/router_linux.h @@ -0,0 +1,38 @@ +#ifndef ROUTERLINUX_H +#define ROUTERLINUX_H + +#include +#include +#include +#include +#include +#include + +/** + * @brief The Router class - General class for handling ip routing + */ +class RouterLinux : public QObject +{ + Q_OBJECT +public: + static RouterLinux& Instance(); + + bool routeAdd(const QString &ip, const QString &gw, const int &sock); + int routeAddList(const QString &gw, const QStringList &ips); + bool clearSavedRoutes(); + bool routeDelete(const QString &ip, const QString &gw, const int &sock); + bool routeDeleteList(const QString &gw, const QStringList &ips); + void flushDns(); + +public slots: + +private: + RouterLinux() {} + RouterLinux(RouterLinux const &) = delete; + RouterLinux& operator= (RouterLinux const&) = delete; + + QList m_addedRoutes; +}; + +#endif // ROUTERLINUX_H + diff --git a/service/server/server.pro b/service/server/server.pro index 6e0e5dd8..4c89ee67 100644 --- a/service/server/server.pro +++ b/service/server/server.pro @@ -46,7 +46,7 @@ LIBS += \ macx { HEADERS += \ - router_mac.h + router_mac.h \ helper_route_mac.h SOURCES += \ @@ -54,6 +54,14 @@ SOURCES += \ helper_route_mac.c } +linux { +HEADERS += \ + router_linux.h + +SOURCES += \ + router_linux.cpp +} + include(../src/qtservice.pri) #CONFIG(release, debug|release) {