#!/bin/bash ############################################################################### # # # IPFire.org - A linux based firewall # # Copyright (C) 2017 IPFire Network Development Team # # # # This program is free software: you can redistribute it and/or modify # # it under the terms of the GNU General Public License as published by # # the Free Software Foundation, either version 3 of the License, or # # (at your option) any later version. # # # # This program is distributed in the hope that it will be useful, # # but WITHOUT ANY WARRANTY; without even the implied warranty of # # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # # GNU General Public License for more details. # # # # You should have received a copy of the GNU General Public License # # along with this program. If not, see . # # # ############################################################################### VPN_SECURITY_POLICIES_CONFIG_SETTINGS="CIPHER COMPRESSION GROUP_TYPE INTEGRITY KEY_EXCHANGE LIFETIME PFS" VPN_SECURITY_POLICIES_READONLY="system" VPN_DEFAULT_SECURITY_POLICY="system" declare -A VPN_SUPPORTED_CIPHERS=( # 3DES-CBC [3DES-CBC]="168 bit 3DES-EDE-CBC" # AES-CBC [AES256-CBC]="256 bit AES-CBC" [AES192-CBC]="192 bit AES-CBC" [AES128-CBC]="128 bit AES-CBC" # AES-CTR [AES256-CTR]="256 bit AES-COUNTER" [AES192-CTR]="192 bit AES-COUNTER" [AES128-CTR]="128 bit AES-COUNTER" # AES-GCM [AES256-GCM128]="256 bit AES-GCM with 128 bit ICV" [AES192-GCM128]="192 bit AES-GCM with 128 bit ICV" [AES128-GCM128]="128 bit AES-GCM with 128 bit ICV" [AES256-GCM96]="256 bit AES-GCM with 96 bit ICV" [AES192-GCM96]="192 bit AES-GCM with 96 bit ICV" [AES128-GCM96]="128 bit AES-GCM with 96 bit ICV" [AES256-GCM64]="256 bit AES-GCM with 64 bit ICV" [AES192-GCM64]="192 bit AES-GCM with 64 bit ICV" [AES128-GCM64]="128 bit AES-GCM with 64 bit ICV" # AES-CCM [AES256-CCM128]="256 bit AES-CCM with 128 bit ICV" [AES192-CCM128]="192 bit AES-CCM with 128 bit ICV" [AES128-CCM128]="128 bit AES-CCM with 128 bit ICV" [AES256-CCM96]="256 bit AES-CCM with 96 bit ICV" [AES192-CCM96]="192 bit AES-CCM with 96 bit ICV" [AES128-CCM96]="128 bit AES-CCM with 96 bit ICV" [AES256-CCM64]="256 bit AES-CCM with 64 bit ICV" [AES192-CCM64]="192 bit AES-CCM with 64 bit ICV" [AES128-CCM64]="128 bit AES-CCM with 64 bit ICV" # CAMELLIA-CBC [CAMELLIA256-CBC]="256 bit CAMELLIA-CBC" [CAMELLIA192-CBC]="192 bit CAMELLIA-CBC" [CAMELLIA128-CBC]="128 bit CAMELLIA-CBC" # CAMELLIA-CTR [CAMELLIA256-CTR]="256 bit CAMELLIA-COUNTER" [CAMELLIA192-CTR]="192 bit CAMELLIA-COUNTER" [CAMELLIA128-CTR]="128 bit CAMELLIA-COUNTER" # CAMELLIA-GCM [CAMELLIA256-GCM128]="256 bit CAMELLIA-GCM with 128 bit ICV" [CAMELLIA192-GCM128]="192 bit CAMELLIA-GCM with 128 bit ICV" [CAMELLIA128-GCM128]="128 bit CAMELLIA-GCM with 128 bit ICV" [CAMELLIA256-GCM96]="256 bit CAMELLIA-GCM with 96 bit ICV" [CAMELLIA192-GCM96]="192 bit CAMELLIA-GCM with 96 bit ICV" [CAMELLIA128-GCM96]="128 bit CAMELLIA-GCM with 96 bit ICV" [CAMELLIA256-GCM64]="256 bit CAMELLIA-GCM with 64 bit ICV" [CAMELLIA192-GCM64]="192 bit CAMELLIA-GCM with 64 bit ICV" [CAMELLIA128-GCM64]="128 bit CAMELLIA-GCM with 64 bit ICV" # CAMELLIA-CCM [CAMELLIA256-CCM128]="256 bit CAMELLIA-CCM with 128 bit ICV" [CAMELLIA192-CCM128]="192 bit CAMELLIA-CCM with 128 bit ICV" [CAMELLIA128-CCM128]="128 bit CAMELLIA-CCM with 128 bit ICV" [CAMELLIA256-CCM96]="256 bit CAMELLIA-CCM with 96 bit ICV" [CAMELLIA192-CCM96]="192 bit CAMELLIA-CCM with 96 bit ICV" [CAMELLIA128-CCM96]="128 bit CAMELLIA-CCM with 96 bit ICV" [CAMELLIA256-CCM64]="256 bit CAMELLIA-CCM with 64 bit ICV" [CAMELLIA192-CCM64]="192 bit CAMELLIA-CCM with 64 bit ICV" [CAMELLIA128-CCM64]="128 bit CAMELLIA-CCM with 64 bit ICV" ) declare -A VPN_SUPPORTED_INTEGRITY=( [MD5]="MD5-HMAC" # SHA [SHA1]="SHA1-HMAC" [SHA512]="512 bit SHA2-HMAC" [SHA384]="384 bit SHA2-HMAC" [SHA256]="256 bit SHA2-HMAC" # AES [AES-XCBC]="AES-XCBC" [AES-CMAC]="AES-CMAC" [AES256-GMAC]="256 bit AES-GMAC" [AES192-GMAC]="192 bit AES-GMAC" [AES128-GMAC]="128 bit AES-GMAC" ) declare -A VPN_SUPPORTED_GROUP_TYPES=( # Regular Groups [MODP768]="768 bit Modulo Prime Group" [MODP1024]="1024 bit Modulo Prime Group" [MODP1536]="1536 bit Modulo Prime Group" [MODP2048]="2048 bit Modulo Prime Group" [MODP3072]="3072 bit Modulo Prime Group" [MODP4096]="4096 bit Modulo Prime Group" [MODP6144]="6144 bit Modulo Prime Group" [MODP8192]="8192 bit Modulo Prime Group" # NIST Elliptic Curve Groups [ECP192]="192 bit NIST Elliptic Curve Group" [ECP224]="224 bit NIST Elliptic Curve Group" [ECP256]="256 bit NIST Elliptic Curve Group" [ECP384]="384 bit NIST Elliptic Curve Group" [ECP521]="521 bit NIST Elliptic Curve Group" # Brainpool Elliptic Curve Groups [ECP224BP]="224 bit Brainpool Elliptic Curve Group" [ECP256BP]="256 bit Brainpool Elliptic Curve Group" [ECP384BP]="384 bit Brainpool Elliptic Curve Group" [ECP512BP]="512 bit Brainpool Elliptic Curve Group" # Curve25519 [CURVE25519]="256 bit Elliptic Curve 25519" ) # This functions checks if a policy is readonly # returns true when yes and false when no vpn_security_policies_check_readonly() { if isoneof name ${VPN_SECURITY_POLICIES_READONLY}; then return ${EXIT_TRUE} else return ${EXIT_FALSE} fi } # This function writes all values to a via ${name} specificated vpn security policy configuration file vpn_security_policies_write_config() { assert [ $# -ge 1 ] local name="${1}" if ! vpn_security_policy_exists "${name}"; then log ERROR "No such vpn security policy: ${name}" return ${EXIT_ERROR} fi if vpn_security_policies_check_readonly "${name}"; then log ERROR "The ${name} vpn security policy cannot be changed." return ${EXIT_ERROR} fi local path="$(vpn_security_policies_path "${name}")" if [ ! -w ${path} ]; then log ERROR "${path} is not writeable" return ${EXIT_ERROR} fi if ! settings_write "${path}" ${VPN_SECURITY_POLICIES_CONFIG_SETTINGS}; then log ERROR "Could not write configuration settings for vpn security policy ${name}" return ${EXIT_ERROR} fi # TODO everytime we successfully write a config we should call some trigger to take the changes into effect } # This funtion writes the value for one key to a via ${name} specificated vpn security policy configuration file vpn_security_policies_write_config_key() { assert [ $# -ge 3 ] local name=${1} local key=${2} shift 2 local value="$@" if ! vpn_security_policy_exists "${name}"; then log ERROR "No such vpn security policy: ${name}" return ${EXIT_ERROR} fi log DEBUG "Set '${key}' to new value '${value}' in vpn security policy ${name}" local ${VPN_SECURITY_POLICIES_CONFIG_SETTINGS} # Read the config settings if ! vpn_security_policies_read_config "${name}"; then return ${EXIT_ERROR} fi # Set the key to a new value assign "${key}" "${value}" if ! vpn_security_policies_write_config "${name}"; then return ${EXIT_ERROR} fi return ${EXIT_TRUE} } # Reads one or more keys out of a settings file or all if no key is provided. vpn_security_policies_read_config() { assert [ $# -ge 1 ] local name="${1}" shift 1 if ! vpn_security_policy_exists "${name}"; then log ERROR "No such vpn security policy: ${name}" return ${EXIT_ERROR} fi local args if [ $# -eq 0 ] && [ -n "${VPN_SECURITY_POLICIES_CONFIG_SETTINGS}" ]; then list_append args ${VPN_SECURITY_POLICIES_CONFIG_SETTINGS} else list_append args $@ fi local path="$(vpn_security_policies_path ${name})" if ! settings_read "${path}" ${args}; then log ERROR "Could not read settings for vpn security policy ${name}" return ${EXIT_ERROR} fi } # Returns the path to a the configuration fora given name vpn_security_policies_path() { assert [ $# -eq 1 ] local name=${1} if vpn_security_policies_check_readonly "${name}"; then echo "${NETWORK_SHARE_DIR}/vpn/security-policies/${name}" else echo "${NETWORK_CONFIG_DIR}/vpn/security-policies/${name}" fi } # Print the content of a vpn security policy configuration file in a nice way vpn_security_policies_show() { assert [ $# -eq 1 ] local name=${1} local ${VPN_SECURITY_POLICIES_CONFIG_SETTINGS} if ! vpn_security_policies_read_config ${name}; then return ${EXIT_ERROR} fi cli_print_fmt1 0 "Security Policy: ${name}" cli_space # This could be done in a loop but a loop is much more complicated # because we print 'Group Types' but the variable is named 'GROUP_TYPES' cli_print_fmt1 1 "Ciphers:" local cipher for cipher in ${CIPHER}; do cli_print_fmt1 2 "${VPN_SUPPORTED_CIPHERS[${cipher}]-${cipher}}" done cli_space cli_print_fmt1 1 "Integrity:" local integrity for integrity in ${INTEGRITY}; do cli_print_fmt1 2 "${VPN_SUPPORTED_INTEGRITY[${integrity}]-${integrity}}" done cli_space cli_print_fmt1 1 "Group Types:" local group_type for group_type in ${GROUP_TYPE}; do cli_print_fmt1 2 "${VPN_SUPPORTED_GROUP_TYPES[${group_type}]-${group_type}}" done cli_space cli_print_fmt1 1 "Key Exchange:" "${KEY_EXCHANGE}" # Key Lifetime if isinteger LIFETIME && [ ${LIFETIME} -gt 0 ]; then cli_print_fmt1 1 "Key Lifetime:" "$(format_time ${LIFETIME})" else log ERROR "The value for Key Lifetime is not a valid integer greater zero." fi # PFS if enabled PFS; then cli_print_fmt1 1 "Perfect Forward Secrecy:" "enabled" else cli_print_fmt1 1 "Perfect Forward Secrecy:" "disabled" fi cli_space # Compression if enabled COMPRESSION; then cli_print_fmt1 1 "Compression:" "enabled" else cli_print_fmt1 1 "Compression:" "disabled" fi cli_space } # This function checks if a vpn security policy exists # Returns True when yes and false when not vpn_security_policy_exists() { assert [ $# -eq 1 ] local name=${1} local path=$(vpn_security_policies_path "${name}") [ -f ${path} ] && return ${EXIT_TRUE} || return ${EXIT_FALSE} } # This function parses the parameters for the 'cipher' command vpn_security_policies_cipher(){ local name=${1} shift if [ $# -eq 0 ]; then log ERROR "You must pass at least one value after cipher" return ${EXIT_ERROR} fi local CIPHER if ! vpn_security_policies_read_config ${name} "CIPHER"; then return ${EXIT_ERROR} fi # Remove duplicated entries to proceed the list safely CIPHER="$(list_unique ${CIPHER})" while [ $# -gt 0 ]; do case "${1}" in -*) value=${1#-} # Check if the cipher is in the list of ciphers and # check if the list has after removing this cipher at least one valid value if list_match ${value} ${CIPHER}; then list_remove CIPHER ${value} else # We do not break here because this error does not break the processing of the next maybe valid values. log ERROR "Can not remove ${value} from the list of Ciphers because ${value} is not in the list." fi ;; +*) value=${1#+} # Check if the Ciphers is in the list of supported ciphers. if ! isoneof value ${!VPN_SUPPORTED_CIPHERS[@]}; then # We do not break here because this error does not break the processing of the next maybe valid values. log ERROR "${value} is not a supported cipher and can thats why not added to the list of ciphers." else if list_match ${value} ${CIPHER}; then log WARNING "${value} is already in the list of ciphers of this policy." else list_append CIPHER ${value} fi fi ;; esac shift done # Check if the list contain at least one valid cipher if [ $(list_length ${CIPHER}) -ge 1 ]; then if ! vpn_security_policies_write_config_key ${name} "CIPHER" ${CIPHER}; then log ERROR "The changes for the vpn security policy ${name} could not be written." fi else log ERROR "After proceding all ciphers the list is empty and thats why no changes are written." return ${EXIT_ERROR} fi } # This function parses the parameters for the 'compression' command vpn_security_policies_compression(){ local name=${1} local value=${2} # Check if we get only one argument after compression if [ ! $# -eq 2 ]; then log ERROR "The number of arguments do not match. Only one argument after compression is allowed." return ${EXIT_ERROR} fi if ! isbool value; then # We suggest only two values to avoid overburding the user. log ERROR "Invalid Argument ${value}" return ${EXIT_ERROR} fi vpn_security_policies_write_config_key "${name}" "COMPRESSION" "${value}" } # This function parses the parameters for the 'group-type' command vpn_security_policies_group_type(){ local name=${1} shift if [ $# -eq 0 ]; then log ERROR "You must pass at least one value after group-type" return ${EXIT_ERROR} fi local GROUP_TYPE if ! vpn_security_policies_read_config ${name} "GROUP_TYPE"; then return ${EXIT_ERROR} fi # Remove duplicated entries to proceed the list safely GROUP_TYPE="$(list_unique ${GROUP_TYPE})" while [ $# -gt 0 ]; do case "${1}" in -*) value=${1#-} # Check if the group type is in the list of group types and # check if the list has after removing this group type at leatst one valid value if list_match ${value} ${GROUP_TYPE}; then list_remove GROUP_TYPE ${value} else # We do not break here because this error does not break the processing of the next maybe valid values. log ERROR "Can not remove ${value} from the list of group types because ${value} is not in the list." fi ;; +*) value=${1#+} # Check if the group type is in the list of supported group types. if ! isoneof value ${!VPN_SUPPORTED_GROUP_TYPES[@]}; then # We do not break here because the processing of other maybe valid values are indepent from this error. log ERROR "${value} is not a supported group type and can thats why not added to the list of group types." else if list_match ${value} ${GROUP_TYPE}; then log WARNING "${value} is already in the list of group-types of this policy." else list_append GROUP_TYPE ${value} fi fi ;; esac shift done # Check if the list contain at least one valid group-type if [ $(list_length ${GROUP_TYPE}) -ge 1 ]; then if ! vpn_security_policies_write_config_key ${name} "GROUP_TYPE" ${GROUP_TYPE}; then log ERROR "The changes for the vpn security policy ${name} could not be written." fi else log ERROR "After proceding all group types the list is empty and thats why no changes are written." return ${EXIT_ERROR} fi } # This function parses the parameters for the 'integrity' command vpn_security_policies_integrity(){ local name=${1} shift if [ $# -eq 0 ]; then log ERROR "You must pass at least one value after integrity." return ${EXIT_ERROR} fi local INTEGRITY if ! vpn_security_policies_read_config ${name} "INTEGRITY"; then return ${EXIT_ERROR} fi # Remove duplicated entries to proceed the list safely INTEGRITY="$(list_unique ${INTEGRITY})" while [ $# -gt 0 ]; do case "${1}" in -*) value=${1#-} # Check if the integrity hash is in the list of integrity hashes and # check if the list has after removing this integrity hash at least one valid value if list_match ${value} ${INTEGRITY}; then list_remove INTEGRITY ${value} else # We do not break here because the processing of other maybe valid values are indepent from this error. log ERROR "Can not remove ${value} from the list of integrity hashes because ${value} is not in the list." fi ;; +*) value=${1#+} # Check if the Ciphers is in the list of supported integrity hashes. if ! isoneof value ${!VPN_SUPPORTED_INTEGRITY[@]}; then # We do not break here because the processing of other maybe valid values are indepent from this error. log ERROR "${value} is not a supported integrity hash and can thats why not added to the list of integrity hashes." else if list_match ${value} ${INTEGRITY}; then log WARNING "${value} is already in the list of integrety hashes of this policy." else list_append INTEGRITY ${value} fi fi ;; esac shift done # Check if the list contain at least one valid group-type if [ $(list_length ${INTEGRITY}) -ge 1 ]; then if ! vpn_security_policies_write_config_key ${name} "INTEGRITY" ${INTEGRITY}; then log ERROR "The changes for the vpn security policy ${name} could not be written." fi else log ERROR "After proceding all integrity hashes the list is empty and thats why no changes are written." return ${EXIT_ERROR} fi } # This function parses the parameters for the 'key-exchange' command vpn_security_policies_key_exchange() { local name=${1} local value=${2} # Check if we get only one argument after key-exchange if [ ! $# -eq 2 ]; then log ERROR "The number of arguments do not match. Only argument after key-exchange is allowed." return ${EXIT_ERROR} fi if ! isoneof value "ikev1" "ikev2" "IKEV1" "IKEV2"; then log ERROR "Invalid Argument ${value}" return ${EXIT_ERROR} fi vpn_security_policies_write_config_key "${name}" "KEY_EXCHANGE" "${value,,}" } # This function parses the parameters for the 'lifetime' command. vpn_security_policies_lifetime(){ local name=${1} shift local value=$@ # Check if we get only one argument after lifetime if [ ! $# -ge 1 ]; then log ERROR "The number of arguments do not match you must provide at least one integer value or a valid time with the format h m s" return ${EXIT_ERROR} fi if ! isinteger value; then value=$(parse_time $@) if [ ! $? -eq 0 ]; then log ERROR "Parsing the passed time was not sucessful please check the passed values." return ${EXIT_ERROR} fi fi if [ ${value} -le 0 ]; then log ERROR "The passed time value must be in the sum greater zero seconds." return ${EXIT_ERROR} fi vpn_security_policies_write_config_key "${name}" "LIFETIME" "${value}" } # This function parses the parameters for the 'pfs' command vpn_security_policies_pfs(){ local name=${1} local value=${2} # Check if we get only one argument after pfs if [ ! $# -eq 2 ]; then log ERROR "The number of arguments do not match. Only argument after pfs is allowed." return ${EXIT_ERROR} fi if [ ! $# -eq 2 ] || ! isbool value; then # We suggest only two values to avoid overburding the user. log ERROR "Invalid Argument ${value}" return ${EXIT_ERROR} fi vpn_security_policies_write_config_key "${name}" "PFS" "${value}" } # This function checks if a vpn security policy name is valid # Allowed are only A-Za-z0-9 vpn_security_policies_check_name() { assert [ $# -eq 1 ] local name=${1} [[ ${name} =~ [^[:alnum:]$] ]] } # Function that creates based on the paramters one ore more new vpn security policies vpn_security_policies_new() { if [ $# -gt 1 ]; then error "Too many arguments" return ${EXIT_ERROR} fi local name="${1}" if ! isset name; then error "Please provide a name" return ${EXIT_ERROR} fi # Check for duplicates if vpn_security_policy_exists "${name}"; then error "The VPN security policy with name ${name} already exists" return ${EXIT_ERROR} fi # Check if name is valid if vpn_security_policies_check_name "${name}"; then error "'${name}' contains illegal characters" return ${EXIT_ERROR} fi # Check if we have a read-only policy with the same name if vpn_security_policies_check_readonly "${name}"; then error "The VPN security policy ${name} is read-only" return ${EXIT_ERROR} fi # Check if our source policy exists if ! vpn_security_policy_exists "${VPN_DEFAULT_SECURITY_POLICY}"; then error "Default VPN Security Policy '${VPN_DEFAULT_SECURITY_POLICY}' does not exist" return ${EXIT_ERROR} fi log DEBUG "Creating VPN Security Policy ${name}" if copy "$(vpn_security_policies_path "${VPN_DEFAULT_SECURITY_POLICY}")" "$(vpn_security_policies_path ${name})"; then log INFO "VPN Security Policy ${name} successfully created" else log ERROR "Could not create VPN Security Policy ${name}" return ${EXIT_ERROR} fi # Show the newly created policy vpn_security_policies_show "${name}" } # Function that deletes based on the passed parameters one ore more vpn security policies vpn_security_policies_destroy() { local name for name in $@; do if ! vpn_security_policy_exists ${name}; then log ERROR "The vpn security policy ${name} does not exist." continue fi if vpn_security_policies_check_readonly ${name}; then log ERROR "The vpn security policy ${name} cannot be deleted." continue fi log DEBUG "Deleting vpn security policy ${name}" settings_remove $(vpn_security_policies_path ${name}) done }