diff options
Diffstat (limited to 'files')
-rwxr-xr-x | files/certreq.sh | 138 |
1 files changed, 120 insertions, 18 deletions
diff --git a/files/certreq.sh b/files/certreq.sh index 132c049..e1638d4 100755 --- a/files/certreq.sh +++ b/files/certreq.sh @@ -6,25 +6,26 @@ # Title: Script that Requests a Certificate from a Microsoft Sub-CA # Purpose: Automate host certificate generation in a domain environment # Package: ansible role certreq -# History: +# History: # 2017-11-22 Add ca cert chain # 2018-04-16 Add --list and --csr options # 2018-05-07 Add actions for using a CA with manually-approved certs # 2018-06-19 Fix get number of ca cert # 2018-07-30 add error checking on the request and authorization # 2018-08-16 update error checking and exit codes -# 2018-09-10 add CERTREQ_OPENSSL_BIN and CERTREQ_OPENSSL_CONF values +# 2018-09-10 add CERTREQ_OPENSSL_BIN and CERTREQ_OPENSSL_CONF values, and SAN support # Usage: in ansible role certreq # Microsoft CA cert templates have permissions on them. A user must be able to "enroll" on the template. # Reference: ftemplate.sh 2017-10-10x; framework.sh 2017-10-09a # fundamental curl statements https://stackoverflow.com/questions/31283476/submitting-base64-csr-to-a-microsoft-ca-via-curl/39722983#39722983 +# subjectaltname in openssl.cnf https://bgstack15.wordpress.com/2017/05/21/generate-certificate-with-subjectaltname-attributes-in-freeipa/ # Improve: fiversion="2017-10-10x" -certreqversion="2018-08-16a" +certreqversion="2018-09-10b" usage() { less -F >&2 <<ENDUSAGE -usage: certreq.sh [-dhV] [-u username] [-p password] [-w tempdir] [-t template] [--cn CN] [--ca <CA hostname>] [-l|-g] [--list|--csr /path/to/file|--fetch|--request] [--no-ca] [--reqid <reqid_string>] +usage: certreq.sh [-dhV] [-u username] [-p password] [-w tempdir] [-t template] [--cn CN] [--ca <CA hostname>] [-l|-g] [--list|--csr /path/to/file|--fetch|--request] [--no-ca] [--reqid <reqid_string>] [--openssl-bin /bin/openssl] [--openssl-conf /opt/openssl.cnf] version ${certreqversion} -d debug Show debugging info, including parsed variables. -h usage Show this usage block. @@ -39,6 +40,8 @@ version ${certreqversion} --no-ca Skip downloading the CA cert chain. --openssl-bin <value> Use this binary for openssl. Default is "openssl" --openssl-conf <value> Use this config for openssl. Default is none. + --dnssans <value> Use a pipe-delimited set of values as subjectAltName dns entries. + --ipsans <value> Use a pipe-delimited set of values as subjectAltName ip entries. ACTIONS: --list list available templates and exit. --csr filename Provide a .csr file instead of making a new csr. Accepts "stdin" to read from standard in. @@ -74,7 +77,7 @@ openssl_req() { local this_csr="${4}" local this_openssl_bin="${5}" local this_openssl_config="${6}" - + debuglev 8 && echo "Action ${this_action}" case "${this_action}" in *-csr) @@ -150,7 +153,7 @@ submit_csr() { -H "Referer: ${this_ca}/certsrv/certrqxt.asp" \ -H 'User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko' \ -H 'Content-Type: application/x-www-form-urlencoded' \ - --data "Mode=newreq&CertRequest=${this_cert}&CertAttrib=${this_cert_attrib}&TargetStoreFlags=0&SaveCert=yes&ThumbPrint=" )" + --data "Mode=newreq&CertRequest=${this_cert}&CertAttrib=${this_cert_attrib}&TargetStoreFlags=0&SaveCert=yes&ThumbPrint=" )" OUTPUTLINK="$( echo "${FULLPAGE}" | grep -A 1 'function handleGetCert() {' | tail -n 1 | cut -d '"' -f 2 )" CERTLINK="${this_ca}/certsrv/${OUTPUTLINK}" ;; @@ -265,7 +268,7 @@ action_get_cert() { # call: action_get_cert "${CERTREQ_CNPARAM}" "${CERTREQ_SUBJECT}" "${CERTREQ_USER}:${CERTREQ_PASS}" "${CERTREQ_CA}" "${CERTREQ_CAHOST}" "${CERTREQ_ACTION}" "${CERTREQ_CSR}" "${CERTREQ_OPENSSL_BIN}" "${CERTREQ_config_string}" # outputs: # vars: ${curloutput} - # files: ${CHAIN_FILE} ${CERTREQ_CNPARAM}.crt and .key and + # files: ${CHAIN_FILE} ${CERTREQ_CNPARAM}.crt and .key and debuglev 9 && ferror "$FUNCNAME $@" local this_cnparam="${1}" @@ -284,9 +287,9 @@ action_get_cert() { echo "CSR=${CSR}" echo "DATA=${DATA}" echo "CERTATTRIB=${CERTATTRIB}" - } + } - # SUBMIT CERTIFICATE SIGNING REQUEST + # SUBMIT CERTIFICATE SIGNING REQUEST submit_csr "${this_user_string}" "${this_ca}" "${this_ca_host}" "${CSR}" "${CERTATTRIB}" "${this_action}" debuglev 8 && { echo "FULLPAGE=${FULLPAGE}" @@ -341,9 +344,9 @@ action_request() { echo "CSR=${CSR}" echo "DATA=${DATA}" echo "CERTATTRIB=${CERTATTRIB}" - } + } - # SUBMIT CERTIFICATE SIGNING REQUEST + # SUBMIT CERTIFICATE SIGNING REQUEST submit_csr "${this_user_string}" "${this_ca}" "${this_ca_host}" "${CSR}" "${CERTATTRIB}" "${this_action}" debuglev 8 && { echo "FULLPAGE=${FULLPAGE}" @@ -352,7 +355,7 @@ action_request() { echo "DISPOSITION=${DISPOSITION}" echo "MESSAGE=${MESSAGE}" } - + } action_fetch() { @@ -393,7 +396,7 @@ action_fetch() { action_list_templates() { # call: action_list_templates "${CERTREQ_USER}:${CERTREQ_PASS}" "${CERTREQ_CA}" "${CERTREQ_CAHOST}" debuglev 9 && ferror "$FUNCNAME $@" - + local this_user_string="${1}" local this_ca="${2}" local this_ca_host="${3}" @@ -413,6 +416,15 @@ action_list_templates() { } +file_section_has_value() { + # call: file_section_has_value "${thisfile}" "${sectionregex}" "${nextsectionregex}" "${valueregex}" + ___fshv_file="${1}" + ___fshv_sectionregex="${2}" + ___fshv_nextsectionregex="${3}" + ___fshv_valueregex="${4}" + test -n "$( sed -n -r -e "/${___fshv_sectionregex}/,/${___fshv_nextsectionregex}/p" "${___fshv_file}" 2>/dev/null | grep -q -r -e "${___fshv_valueregex}" )" +} + # DEFINE TRAPS clean_certreq() { @@ -464,8 +476,10 @@ parseFlag() { "req" | "reqid" | "req-id" | "request" | "requestid" | "request-id" ) getval; CERTREQ_REQID="${tempval}";; "openssl-bin" | "openssl" | "opensslbin" | "openssl-binary" | "opensslexec" | "openssl-exec" ) getval; CERTREQ_OPENSSL_BIN="${tempval}";; "openssl-conf" | "opensslconf" | "openssl_conf" ) getval; CERTREQ_OPENSSL_CONF="${tempval}";; + "dnssans" | "dns-sans" | "dnssan" | "dns-san" ) getval; CERTREQ_DNSSANS="${tempval}";; + "ipsans" | "ip-sans" | "ipsan" | "ip-san" ) getval; CERTREQ_IPSANS="${tempval}";; esac - + debuglev 10 && { test ${hasval} -eq 1 && ferror "flag: ${flag} = ${tempval}" || ferror "flag: ${flag}"; } } @@ -558,7 +572,9 @@ logfile="$( TMPDIR="${CERTREQ_WORKDIR}" mktemp -t tmp.XXXXXXXXXX )" CERTREQ_TEMPFILE="$( TMPDIR="${CERTREQ_WORKDIR}" mktemp -t tmp.XXXXXXXXXX )" define_if_new CERTREQ_ACTION "generate" define_if_new CERTREQ_OPENSSL_BIN "openssl" -# no CERTREQ_OPENSSL_CONF defined by default. Just use the system default unless specified. +# no default CERTREQ_OPENSSL_CONF. Just use system default. +# no default CERTREQ_DNSSANS, which is pipe-delimited. +# no default CERTREQ_IPSANS, which is pipe-delimited. # calculate the subject if test -n "${CERTREQ_CNPARAM}"; @@ -586,10 +602,13 @@ define_if_new CERTREQ_CA "http://ca2.ad.example.com" # generate cahost CERTREQ_CAHOST="$( echo "${CERTREQ_CA}" | sed -r -e 's/https?:\/\///g' -e 's/(\.[a-z]{2,3})\/$/\1/;' )" -# calculate openssl_conf string if necessary +# verify openssl_conf dependency if test -n "${CERTREQ_OPENSSL_CONF}" ; then - CERTREQ_openssl_config="-config ${CERTREQ_OPENSSL_CONF}" + if ! test -r "${CERTREQ_OPENSSL_CONF}" ; + then + ferror "${scriptfile}: 1004. CERTREQ_OPENSSL_CONF file ${CERTREQ_OPENSSL_CONF} is invalid or not found. Aborted." && exit 1004 + fi fi ## REACT TO BEING A CRONJOB @@ -605,6 +624,87 @@ fi #trap "CTRLZ" 18 trap "clean_certreq" 0 +# PREPARE CUSTOM CONF FILE WITH ANY SUBJECTALTNAME ENTRIES +if test -n "${CERTREQ_DNSSANS}${CERTREQ_IPSANS}" ; +then + + # initialize new conf file + CERTREQ_openssl_conf_new="$( TMPDIR="${CERTREQ_WORKDIR}" mktemp -t openssl.cnf.XXXXXXXXXX )" + + # select conf file + if test -z "${CERTREQ_OPENSSL_CONF}" ; + then + # need to calculate the default conf file + CERTREQ_first_found="$( find /etc/pki/tls/openssl.cnf /usr/local/ssl/openssl.cnf 2>/dev/null | head -n1 )" + if ! test -r "${CERTREQ_first_found}" ; + then + ferror "${scriptfile}: 1004. Cannot determine default openssl.cnf for inserting SANs. Aborted." && exit 1004 + else + CERTREQ_OPENSSL_CONF="${CERTREQ_first_found}" && unset CERTREQ_first_found + fi + fi + + # copy in contents + /bin/cp -p "${CERTREQ_OPENSSL_CONF}" "${CERTREQ_openssl_conf_new}" + CERTREQ_OPENSSL_CONF="${CERTREQ_openssl_conf_new}" && unset CERTREQ_openssl_conf_new + + # make modifications, add v3_req + #if test -z "$( sed -n -r -e '/^\s*\[ req \]/,/^\s*\[/p' "${CERTREQ_OPENSSL_CONF}" 2>/dev/null | grep -r -e '^\s*req_extensions' )" ; + if ! file_section_has_value "${CERTREQ_OPENSSL_CONF}" "^\s*\[ req \]" "^\s*\[" "^\s*req_extensions" ; + then + # need to add req_extensions = v3_req to [ req ] + # get line number of [ req ] + CERTREQ_line_num="$( awk '/^\s*\[ req \]/{print FNR}' "${CERTREQ_OPENSSL_CONF}" 2>/dev/null )" + if test -z "${CERTREQ_line_num}" ; + then + # no line containing + echo "[ req ]" >> "${CERTREQ_OPENSSL_CONF}" + CERTREQ_line_num="$( wc -l < "${CERTREQ_OPENSSL_CONF}" )" + fi + sed -i -r -e "${CERTREQ_line_num}areq_extensions = v3_req" "${CERTREQ_OPENSSL_CONF}" + fi + + # make modifications, add SAN to [ v3_req ] + #if test -z "$( sed -n -r -e '/^\s*\[ v3_req \]/,/^\s*\[/p' "${CERTREQ_OPENSSL_CONF}" 2>/dev/null | grep + if ! file_section_has_value "${CERTREQ_OPENSSL_CONF}" "^\s*\[ v3_req \]" "^\s*\[" "^\s*subjectAltName" ; + then + # need to add it + CERTREQ_line_num="$( awk '/^\s*\[ v3_req \]/{print FNR}' "${CERTREQ_OPENSSL_CONF}" 2>/dev/null )" + if test -z "${CERTREQ_line_num}" ; + then + # need to add the section too + echo "[ v3_req ]" >> "${CERTREQ_OPENSSL_CONF}" + CERTREQ_line_num="$( wc -l < "${CERTREQ_OPENSSL_CONF}" )" + fi + sed -i -r -e "${CERTREQ_line_num}asubjectAltName = @alt_names" "${CERTREQ_OPENSSL_CONF}" + fi + + # make modifications, add subject alt names section + # start by preparing the exact string to make + CERTREQ_san_lines="$( test -n "${CERTREQ_DNSSANS}" && echo "${CERTREQ_DNSSANS}" | tr '|' '\n' | awk 'BEGIN{a=0} {a=a+1 ; print "DNS."a" = "$0 ;}' ; test -n "${CERTREQ_IPSANS}" && echo "${CERTREQ_IPSANS}" | tr '|' '\n' | awk 'BEGIN{a=0} {a=a+1 ; print "IP."a" = "$0 ;}')" + # add to file + if ! file_section_has_value "${CERTREQ_OPENSSL_CONF}" "^\s*\[alt_names\]" "^\s*\[" "=" ; + then + # need to add the values + CERTREQ_line_num="$( awk '/^\s*\[alt_names\]/{print FNR}' "${CERTREQ_OPENSSL_CONF}" 2>/dev/null )" + if test -z "${CERTREQ_line_num}" ; + then + # need to add the section too + echo "[alt_names]" >> "${CERTREQ_OPENSSL_CONF}" + CERTREQ_line_num="$( wc -l < "${CERTREQ_OPENSSL_CONF}" )" + fi + echo "${CERTREQ_san_lines}" | while read line ; do sed -i -r -e "${CERTREQ_line_num}a${line}" "${CERTREQ_OPENSSL_CONF}" ; CERTREQ_line_num=$(( CERTREQ_line_num + 1 )) ; done + fi + + # clean up + unset CERTREQ_line_num +fi + +if test -n "${CERTREQ_OPENSSL_CONF}" ; +then + CERTREQ_openssl_config="-config ${CERTREQ_OPENSSL_CONF}" +fi + # DEBUG SIMPLECONF debuglev 5 && { ferror "Using values" @@ -627,7 +727,7 @@ debuglev 5 && { list) action_list_templates "${CERTREQ_USER}:${CERTREQ_PASS}" "${CERTREQ_CA}" "${CERTREQ_CAHOST}" ;; - + request) # alias of "request-only" action_request "${CERTREQ_CNPARAM}" "${CERTREQ_SUBJECT}" "${CERTREQ_USER}:${CERTREQ_PASS}" "${CERTREQ_CA}" "${CERTREQ_CAHOST}" "${CERTREQ_ACTION}" "${CERTREQ_CSR}" "${CERTREQ_OPENSSL_BIN}" "${CERTREQ_openssl_config}" @@ -680,6 +780,7 @@ case "${CERTREQ_ACTION}" in request) echo "workdir: ${CERTREQ_WORKDIR}" echo "logfile: ${logfile}" + test -n "${CERTREQ_OPENSSL_CONF}" && echo "openssl_conf: ${CERTREQ_OPENSSL_CONF}" echo "csr: ${CERTREQ_WORKDIR}/${CERTREQ_CNPARAM}.csr" echo "key: ${CERTREQ_WORKDIR}/${CERTREQ_CNPARAM}.key" echo "reqid: ${REQUESTID}" @@ -699,6 +800,7 @@ case "${CERTREQ_ACTION}" in # for generate and generate-csr and everything else really echo "workdir: ${CERTREQ_WORKDIR}" echo "logfile: ${logfile}" + test -n "${CERTREQ_OPENSSL_CONF}" && echo "openssl_conf: ${CERTREQ_OPENSSL_CONF}" echo "csr: ${CERTREQ_WORKDIR}/${CERTREQ_CNPARAM}.csr" echo "certificate: ${CERTREQ_WORKDIR}/${CERTREQ_CNPARAM}.crt" echo "key: ${CERTREQ_WORKDIR}/${CERTREQ_CNPARAM}.key" |