#!/bin/bash # Copyright: 2023 Virtuozzo International GmbH # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. VERSION="1"; $PROGRAM 'sed'; $PROGRAM 'grep'; JAVA_HOME=$(readlink -f /usr/bin/java | $SED "s:bin/java::"); DESCRIPTION="Manipulate keystore certificates"; DEFAULT_ACTION="Usage"; SSL_CONFIG_DIR="/var/lib/jelastic/SSL"; $PROGRAM 'wget'; DEFAULT_DOMAIN="jelastic"; $PROGRAM 'openssl'; _OPENSSL=$OPENSSL; _KEYTOOL="${JAVA_HOME}/bin/keytool"; crtUploaded='cert'; crtcaUploaded='intermediate'; keyUploaded='key'; include output; function getKeyPass() { echo "changeit"; } function getStoragePass() { echo "changeit"; } function doUsage() { showUsageMessage; } function checkSSLDirectory(){ [ ! -d "$SSL_CONFIG_DIR" ] && mkdir -p "$SSL_CONFIG_DIR"; } function getCommonNameFromCertificate(){ local common_name=$($OPENSSL x509 -noout -in ${SSL_CONFIG_DIR}/${DEFAULT_DOMAIN}.crt -subject 2>>$ACTIONS_LOG | $GREP -i "subject.*CN=.*" | $SED -n "s/.*CN=\(.*\)\/.*/\1/p"); [ ! -z "$common_name" ] && echo "$common_name" || exit 4; } function getDNSAltNamesFromCertificate(){ local dns_alt_names=$($OPENSSL x509 -text -noout -in ${SSL_CONFIG_DIR}/${DEFAULT_DOMAIN}.crt -subject 2>>$ACTIONS_LOG | $GREP -A 1 "Subject Alternative Name" | tail -n 1 | $SED "s/[.\ ]\{0,\}DNS://g"); [ ! -z "$dns_alt_names" ] && echo "$dns_alt_names" || exit 4; } function getExpireDateFromCertificate(){ local expiredate_raw=$($OPENSSL x509 -text -noout -in ${SSL_CONFIG_DIR}/${DEFAULT_DOMAIN}.crt -subject -enddate 2>>$ACTIONS_LOG | $GREP notAfter | $SED 's/^notAfter=//'); local expiredate=$(date --utc --date="$expiredate_raw" "+%Y-%m-%d %H:%M:%S"); [ ! -z "$expiredate" ] && echo "$expiredate" || exit 4; } # return: # 1 - search string not found in provided file # 2 - empty file function validateCert(){ local crtFL=$1 local searchString=$2 local exitCode=0 if [ -s "$crtFL" ]; then $GREP -qi ${searchString} ${crtFL} || exitCode=1 else exitCode=2 fi return $exitCode } function checkIfKeyEncrypted(){ local crtFL=$1 local searchString=$2 $GREP -qi ${searchString} ${crtFL} && return 1 openssl pkey -in ${crtFL} -passin pass:$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 8) -noout 2>&1 return $? } function doDownloadKeys(){ local domain="$DEFAULT_DOMAIN"; if [ -f "${SSL_CONFIG_DIR}/${crtUploaded}" -a -f "${SSL_CONFIG_DIR}/${crtcaUploaded}" -a -f "${SSL_CONFIG_DIR}/${keyUploaded}" ]; then mv -f ${SSL_CONFIG_DIR}/${crtUploaded} $SSL_CONFIG_DIR/${domain}.crt; mv -f ${SSL_CONFIG_DIR}/${crtcaUploaded} $SSL_CONFIG_DIR/${domain}-ca.crt; mv -f ${SSL_CONFIG_DIR}/${keyUploaded} $SSL_CONFIG_DIR/${domain}.key; elif [ -f "${SSL_CONFIG_DIR}/customssl.conf" ]; then cp -f "${SSL_CONFIG_DIR}/customssl.conf" "${SSL_CONFIG_DIR}/${domain}.conf"; source "$SSL_CONFIG_DIR/${domain}.conf"; $WGET --no-check-certificate "$cert" --output-document="$SSL_CONFIG_DIR/${domain}.crt" > /dev/null 2>&1 $WGET --no-check-certificate "$intermediate" --output-document="$SSL_CONFIG_DIR/${domain}-ca.crt" > /dev/null 2>&1 $WGET --no-check-certificate "$key" --output-document="$SSL_CONFIG_DIR/${domain}.key" > /dev/null 2>&1 else writeJSONResponseErr "result=>4147" "message=>Certificates not uploaded"; echo 147 >${resultFile:-/tmp/sslerror}; die -q; exit 147; fi cert="$SSL_CONFIG_DIR/${domain}.crt"; intermediate="$SSL_CONFIG_DIR/${domain}-ca.crt"; key="$SSL_CONFIG_DIR/${domain}.key"; validateCert "$SSL_CONFIG_DIR/${domain}.crt" "CERTIFICATE" local res=$? case $res in 1 ) writeJSONResponseErr "result=>4011" "message=>Uploaded file isn't a certificate!"; echo 211 >${resultFile:-/tmp/sslerror}; die -q; exit 211; ;; 2 ) writeJSONResponseErr "result=>4012" "message=>Could not receive certificate!"; echo 212 >${resultFile:-/tmp/sslerror}; die -q; exit 212; ;; esac validateCert "$SSL_CONFIG_DIR/${domain}-ca.crt" "CERTIFICATE" res=$? case $res in 1 ) writeJSONResponseErr "result=>4011" "message=>Uploaded file isn't a certificate!"; echo 211 >${resultFile:-/tmp/sslerror}; die -q; exit 211; ;; 2 ) writeJSONResponseErr "result=>4013" "message=>Could not receive CA certificate"; echo 213 >${resultFile:-/tmp/sslerror}; die -q; exit 213; ;; esac validateCert "$SSL_CONFIG_DIR/${domain}.key" "KEY" res=$? case $res in 1 ) writeJSONResponseErr "result=>4015" "message=>Uploaded file isn't a private key!"; echo 215 >${resultFile:-/tmp/sslerror}; die -q; exit 215; ;; 2 ) writeJSONResponseErr "result=>4016" "message=>Could not receive private key!"; echo 216 >${resultFile:-/tmp/sslerror}; die -q; exit 216; ;; esac checkIfKeyEncrypted "$SSL_CONFIG_DIR/${domain}.key" "ENCRYPTED" res=$? [ "$res" -ne "0" ] && { writeJSONResponseErr "result=>4180" "message=>Uploaded key is encrypted!"; echo 217 >${resultFile:-/tmp/sslerror}; die -q; exit 217; } local cert_mod=$(${OPENSSL} x509 -noout -modulus -in ${cert} 2>>$ACTIONS_LOG) local key_mod=$(${OPENSSL} rsa -noout -modulus -in ${key} 2>>$ACTIONS_LOG) [[ "${cert_mod}" != "${key_mod}" ]] && { writeJSONResponseErr "result=>4014" "message=>Certificate does not match key!"; echo 214 >${resultFile:-/tmp/sslerror}; die -q; exit 214; } return 0; } function addSSLToKeystore() { [ $# -ne 1 ] && doUsage; local domain="$1"; local tmpfile=$(mktemp); local keystore="$SSL_CONFIG_DIR/${domain}.jks" #make pkcs12 $OPENSSL pkcs12 -export -in "$cert" -inkey "$key" -out "$tmpfile" -name "$domain" -caname root -password pass:$(getKeyPass) -certfile "$intermediate" || { return 1; } ${_KEYTOOL} -importKEYSTORE -noprompt -deststorepass $(getStoragePass) -destkeypass $(getKeyPass) -destKEYSTORE "$keystore" -srcKEYSTORE "$tmpfile" -srcstoretype PKCS12 -srcstorepass $(getStoragePass) -alias "$domain" > /dev/null 2>&1 || { return 1; } echo y | ${_KEYTOOL} -import -trustcacerts -alias intermed -file ${intermediate} -KEYSTORE ${keystore} -keypass $(getKeyPass) -storepass $(getStoragePass) > /dev/null 2>&1; return $?; } function removeSSL(){ [ $# -ne 1 ] && doUsage; local domain="$1"; [ -d "$SSL_CONFIG_DIR" ] && { rm -Rf $SSL_CONFIG_DIR/*.crt; rm -Rf $SSL_CONFIG_DIR/*.key; rm -Rf $SSL_CONFIG_DIR/*.jks; } } function doCheck() { local domain=${1} cert_DOMAIN cert_mod key_mod cert_DOMAIN=$(${OPENSSL} x509 -noout -text -in ${cert} 2>>$ACTIONS_LOG | $GREP "Subject.*CN=.*" | $SED 's/.*CN=\(.*\)/\1/'); cert_mod=$(${OPENSSL} x509 -noout -modulus -in ${cert} 2>>$ACTIONS_LOG) key_mod=$(${OPENSSL} rsa -noout -modulus -in ${key} 2>>$ACTIONS_LOG) [[ "${domain}" != "${cert_DOMAIN}" ]] && { #runIfExists log "invalid domain"; return 1; } [[ "${cert_mod}" != "${key_mod}" ]] && { #runIfExists log "key did not matching cert"; return 1; } return 0; } function doInstall(){ removeSSL "$DEFAULT_DOMAIN"; doDownloadKeys "$DEFAULT_DOMAIN"; local domain=$(getCommonNameFromCertificate); checkSSLDirectory; addSSLToKeystore "$DEFAULT_DOMAIN" ; } function doRemove(){ removeSSL "$DEFAULT_DOMAIN" ; } function doCheckDomain(){ [ -f "${SSL_CONFIG_DIR}/${DEFAULT_DOMAIN}.crt" ] || { writeJSONResponseErr "result=>4010" "message=>No certificates installed"; return 1; }; local domain=$(getCommonNameFromCertificate); local dns_alt_names=$(getDNSAltNamesFromCertificate 2>/dev/null); local expiredate=$(getExpireDateFromCertificate); [ ! -z "$dns_alt_names" ] && [ ! -z "$expiredate" ] && { writeJSONResponseOut "result=>0" "domain=>${dns_alt_names}" "expiredate=>${expiredate}"; } || { writeJSONResponseErr "result=>4009" "message=>Could not detect valid domain for uploaded certificate"; return 1; }; } function describeCheck() { echo "Check certificate"; } function describeCheckDomain() { echo "Check domain name for uploaded certificate"; } function describeDownloadKeys() { echo ""; } function describeInstall() { echo "Install ssl keys"; } function describeRemove() { echo "Remove ssl certificate"; }