aboutsummaryrefslogtreecommitdiff
path: root/cepceslib.sh
diff options
context:
space:
mode:
authorB. Stack <bgstack15@gmail.com>2024-09-13 15:10:07 -0400
committerB. Stack <bgstack15@gmail.com>2024-09-13 15:10:07 -0400
commite7cfa31919e588d473510109392b35e4d690ac2e (patch)
tree740ae3097b8c2fa2680e349c95fa04f7f1d24c4c /cepceslib.sh
downloadcepceslib-e7cfa31919e588d473510109392b35e4d690ac2e.tar.gz
cepceslib-e7cfa31919e588d473510109392b35e4d690ac2e.tar.bz2
cepceslib-e7cfa31919e588d473510109392b35e4d690ac2e.zip
initial commit
Diffstat (limited to 'cepceslib.sh')
-rwxr-xr-xcepceslib.sh278
1 files changed, 278 insertions, 0 deletions
diff --git a/cepceslib.sh b/cepceslib.sh
new file mode 100755
index 0000000..b461c1a
--- /dev/null
+++ b/cepceslib.sh
@@ -0,0 +1,278 @@
+#!/bin/sh
+# vim: set noet sts=4 sw=4 ts=4:
+# File: cepceslib.sh
+# Location: https://bgstack15.ddns.net/cgit/cepceslib
+# Author: bgstack15, Sathvik Kolla
+# SPDX-License-Identifier: GPL-3.0-only
+# Startdate: 2024-08-23 08:21
+# Title: CES username enrollment
+# Purpose:
+# History:
+# Usage:
+# Reference:
+# https://gist.github.com/leechristensen/28e4ddf89d77b70fe3e694684374c8a5
+# https://www.server-world.info/en/note?os=Windows_Server_2022&p=iis&f=8
+# https://stackoverflow.com/questions/3765212/an-error-occurred-when-verifying-security-for-the-message
+# https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wstep/a22e793f-2c7e-4f5e-a22c-f05c49535855
+# chatgpt for wsse:usernametoken fields and all xmlns entries
+# https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-xcep/3642fda9-8de2-417a-adad-9d368ffe8fc2
+# https://medium.com/@fmcalbuquerque/python-elementtree-xml-api-with-dynamic-namespaces-171d9c9f391e
+# Improve:
+# use env vars for CN and SANs
+# Dependencies:
+# openssl, python3
+# Documentation: README.md
+
+gen_csr() {
+ # input env vars: KEYFILE, CSRFILE, TEMPLATE
+ _cnf="$( mktemp )"
+ cat >"${_cnf}" <<EOFCONF
+oid_section = new_oids
+[ req ]
+prompt = no
+default_bits = 4096
+default_md = sha256
+default_keyfile = privkey.pem
+distinguished_name = req_distinguished_name
+req_extensions = req_ext
+
+[ new_oids ]
+certificateTemplateName = 1.3.6.1.4.1.311.20.2
+
+[ req_distinguished_name ]
+C = US
+ST = New York
+L = New York
+O = Example Organization
+# Important value
+CN = $( hostname -f )
+#emailAddress = noreply@example.com
+
+[ req_ext ]
+basicConstraints = CA:FALSE
+keyUsage = digitalSignature, keyEncipherment
+subjectAltName = @alt_names
+certificateTemplateName = ASN1:UTF8STRING:${TEMPLATE}
+
+[ alt_names ]
+# Important value
+DNS.1 = $( hostname -f )
+DNS.2 = $( hostname -s )
+EOFCONF
+ # generate the csr
+ openssl req -config "${_cnf}" -new -key "${KEYFILE}" -out "${CSRFILE}"
+ # end of gen_csr
+ rm "${_cnf}"
+}
+
+gen_ces() {
+ # input env vars: CSRFILE, CESFILE, CESURL, CESUSER, CESPASSWORD
+ # strip header/footer and make it on a single line
+ _csr_contents="$( sed -r -e '/^-----/d' "${CSRFILE}" | tr -d '\n' )"
+ cat >"${CESFILE}" <<EOFCES
+ <s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing"
+ xmlns:s="http://www.w3.org/2003/05/soap-envelope"
+ xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
+ <s:Header>
+ <a:Action s:mustUnderstand="1">http://schemas.microsoft.com/windows/pki/2009/01/enrollment/RST/wstep</a:Action>
+ <a:MessageID>urn:uuid:$( uuidgen )</a:MessageID>
+ <a:To s:mustUnderstand="1">${CESURL}</a:To>
+ <a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo>
+ <wsse:Security s:mustUnderstand="1">
+ <wsse:UsernameToken>
+ <wsse:Username>${CESUSER}</wsse:Username>
+ <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">${CESPASSWORD}</wsse:Password>
+ </wsse:UsernameToken>
+ </wsse:Security>
+ </s:Header>
+ <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+ <RequestSecurityToken PreferredLanguage="en-US" xmlns="http://docs.oasis-open.org/ws-sx/ws-trust/200512">
+ <TokenType>http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3</TokenType>
+ <RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</RequestType>
+ <BinarySecurityToken ValueType="http://schemas.microsoft.com/windows/pki/2009/01/enrollment#PKCS10" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd#base64binary" a:Id=""
+ xmlns:a="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
+ xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
+ ${_csr_contents}
+ </BinarySecurityToken>
+ <RequestID xsi:nil="true" xmlns="http://schemas.microsoft.com/windows/pki/2009/01/enrollment" />
+ </RequestSecurityToken>
+ </s:Body>
+ </s:Envelope>
+EOFCES
+}
+
+submit_ces_request() {
+ # input env vars: CESURL, CESFILE
+ # -k for irony
+ curl --silent \
+ "${CESURL}" \
+ -H "Content-Type: application/soap+xml" \
+ -X POST \
+ --data @"${CESFILE}" \
+ -k
+}
+
+parse_ces_response() {
+ # input env vars: CESRESPONSEFILE
+ {
+ printf '%s\r\n' '-----BEGIN PKCS7-----'
+ python3 <<-EOFPYTHON
+import xml.etree.ElementTree as ET, sys
+rf = "${CESRESPONSEFILE}"
+tree = ET.parse(rf)
+print(tree.findall(".//*[@ValueType='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd#PKCS7']")[0].text)
+EOFPYTHON
+ printf '%s\r\n' '-----END PKCS7-----'
+ } | grep -E '.' | openssl pkcs7 -in /dev/stdin -print_certs
+}
+
+use_ces() {
+ # input env vars: KEYFILE, CSRFILE, CESURL, CESPASSWORD/CESPASSWODFILE, TEMPLATE, CERTFILE
+ # optional: CESFILE, CESRESPONSEFILE
+ unset _used_temp_crf _used_temp_cf
+ test -z "${KEYFILE}" && { echo "Fatal! Need KEYFILE. Aborted." 1>&2 ; return 1 ; }
+ test -z "${CSRFILE}" && { echo "Fatal! Need CSRFILE. Aborted." 1>&2 ; return 1 ; }
+ test -z "${CESURL}" && { echo "Fatal! Need CESURL. Aborted." 1>&2 ; return 1 ; }
+ test -z "${CESUSER}" && { echo "Fatal! Need CESUSER. Aborted." 1>&2 ; return 1 ; }
+ test -z "${TEMPLATE}" && { echo "Fatal! Need TEMPLATE. How about WebServerV3? Aborted." 1>&2 ; return 1 ; }
+ test -z "${CESPASSWORD}" && test -z "${CESPASSWORDFILE}" && { echo "Fatal! Need CESPASSWORD or CESPASSWORDFILE. Aborted." 1>&2 ; return 1 ; }
+ test -n "$( cat "${CESPASSWORDFILE}" 2>/dev/null )" && CESPASSWORD="$( cat "${CESPASSWORDFILE}" )"
+ test -z "${CESPASSWORD}" && { echo "Fatal! Need CESPASSWORD or CESPASSWORDFILE populated. Aborted." 1>&2 ; return 1 ; }
+ test -z "${CESRESPONSEFILE}" && { CESRESPONSEFILE="$( mktemp )" ; _used_temp_crf=1 ; echo "Using CESRESPONSEFILE=${CESRESPONSEFILE}" 1>&2 ; }
+ test -z "${CERTFILE}" && { echo "Fatal! Need CERTFILE. Aborted." 1>&2 ; return 1 ; }
+ test -z "$( cat "${KEYFILE}" 2>/dev/null )" && {
+ # need to generate a new key
+ openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out "${KEYFILE}"
+ }
+ # so now we have KEYFILE. Lets assume we need to make CSRFILE
+ test -z "$( cat "${CSRFILE}" 2>/dev/null )" && {
+ gen_csr # will produce CSRFILE
+ }
+ test -z "${CESFILE}" && { CESFILE="$( mktemp )" ; _used_temp_cf=1 ; echo "Using CESFILE=${CESFILE}" 1>&2 ; }
+ test -z "$( cat "${CESFILE}" 2>/dev/null )" && {
+ gen_ces # will populate CESFILE
+ }
+ test -z "${SKIP_SUBMIT}" && {
+ submit_ces_request > "${CESRESPONSEFILE}"
+ }
+ test -n "$( cat "${CESRESPONSEFILE}" 2>/dev/null )" && {
+ parse_ces_response > "${CERTFILE}"
+ }
+ # CLEANUP
+ test -n "${_used_temp_crf}" && rm -f "${CESRESPONSEFILE:-NOTHINGTODEL}" 1>/dev/null 2>&1
+ test -n "${_used_temp_cf}" && rm -f "${CESFILE:-NOTHINGTODEL}" 1>/dev/null 2>&1
+ test -z "${NO_CLEAN}" && {
+ rm -f "${CESRESPONSEFILE}" "${CESFILE}" 1>/dev/null 2>&1
+ }
+}
+
+gen_cep() {
+ # input env vars: CEPFILE, CEPURL, CESUSER, CESPASSWORD
+ cat >"${CEPFILE}" <<EOFCEP
+ <s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing"
+ xmlns:s="http://www.w3.org/2003/05/soap-envelope"
+ xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
+ xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
+ <s:Header>
+ <a:Action s:mustUnderstand="1">http://schemas.microsoft.com/windows/pki/2009/01/enrollmentpolicy/IPolicy/GetPolicies</a:Action>
+ <a:MessageID>urn:uuid:$( uuidgen )</a:MessageID>
+ <a:To s:mustUnderstand="1">${CEPURL}</a:To>
+ <a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo>
+ <wsse:Security s:mustUnderstand="1">
+ <wsse:UsernameToken>
+ <wsse:Username>${CESUSER}</wsse:Username>
+ <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">${CESPASSWORD}</wsse:Password>
+ </wsse:UsernameToken>
+ </wsse:Security>
+ </s:Header>
+ <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <GetPolicies xmlns="http://schemas.microsoft.com/windows/pki/2009/01/enrollmentpolicy">
+ <client>
+ <lastUpdate>0001-01-01T00:00:00</lastUpdate>
+ <preferredLanguage xsi:nil="true"></preferredLanguage>
+ </client>
+ <requestFilter xsi:nil="true"></requestFilter>
+ </GetPolicies>
+ </s:Body>
+ </s:Envelope>
+EOFCEP
+}
+
+submit_cep_request() {
+ # input env vars: CEPURL, CEPFILE
+ curl --silent \
+ "${CEPURL}" \
+ -H "Content-Type: application/soap+xml; charset=utf-8" \
+ -X POST \
+ --data @"${CEPFILE}" \
+ -k
+}
+
+parse_cep_response() {
+ # input env vars: CEPRESPONSEFILE
+ # You might be tempted to use ElementTree.register_namespace(), but the author was unable to make that work here, and findall(,namespaces={"ns1":ns}) is not shorter than what is used here and was not tested.
+ python3 <<EOFPYTHON
+import xml.etree.ElementTree as ET, sys
+rf = "${CEPRESPONSEFILE}"
+ns = "{http://schemas.microsoft.com/windows/pki/2009/01/enrollmentpolicy}"
+tree = ET.parse(rf)
+print("endpoints:" + ','.join([i.text for i in tree.findall(f".//{ns}GetPoliciesResponse/{ns}cAs/{ns}cA/{ns}uris/{ns}cAURI/{ns}uri")]))
+for p in tree.findall(f".//{ns}policies/{ns}policy"):
+ if p.find(f"./{ns}attributes/{ns}permission/{ns}enroll").text.lower() in ["true","t","1","yes","y"]:
+ print(p.find(f"./{ns}attributes/{ns}commonName").text)
+EOFPYTHON
+}
+
+use_cep() {
+ # input env vars: CEPURL, CESUSER, CESPASSWORD
+ # optional: CEPFILE, CEPRESPONSEFILE
+ unset _used_temp_cf _used_temp_crf
+ test -z "${CEPURL}" && { echo "Fatal! Need CEPURL. Aborted." 1>&2 ; return 1 ; }
+ test -z "${CESPASSWORD}" && test -z "${CESPASSWORDFILE}" && { echo "Fatal! Need CESPASSWORD or CESPASSWORDFILE. Aborted." 1>&2 ; return 1 ; }
+ test -n "$( cat "${CESPASSWORDFILE}" 2>/dev/null )" && CESPASSWORD="$( cat "${CESPASSWORDFILE}" )"
+ test -z "${CESPASSWORD}" && { echo "Fatal! Need CESPASSWORD or CESPASSWORDFILE populated. Aborted." 1>&2 ; return 1 ; }
+ # process
+ test -z "${CEPFILE}" && { CEPFILE="$( mktemp )" ; _used_temp_cf=1 ; echo "Using CEPFILE=${CEPFILE}" 1>&2 ; }
+ test -z "$( cat "${CEPFILE}" 2>/dev/null )" && {
+ gen_cep # will populate CEPFILE
+ }
+ test -z "${CEPRESPONSEFILE}" && { CEPRESPONSEFILE="$( mktemp )" ; _used_temp_crf=1 ; echo "Using CEPRESPONSEFILE=${CEPRESPONSEFILE}" 1>&2 ; }
+ test -z "${SKIP_SUBMIT}" && {
+ submit_cep_request > "${CEPRESPONSEFILE}"
+ }
+ test -n "$( cat "${CEPRESPONSEFILE}" 2>/dev/null )" && {
+ parse_cep_response # will print available templates
+ }
+ # CLEANUP
+ test -n "${_used_temp_crf}" && rm -f "${CEPRESPONSEFILE:-NOTHINGTODEL}" 1>/dev/null 2>&1
+ test -n "${_used_temp_cf}" && rm -f "${CEPFILE:-NOTHINGTODEL}" 1>/dev/null 2>&1
+ test -z "${NO_CLEAN}" && {
+ rm -f "${CEPRESPONSEFILE}" "${CEPFILE}" 1>/dev/null 2>&1
+ }
+}
+
+# https://stackoverflow.com/questions/2683279/how-to-detect-if-a-script-is-being-sourced
+# BEGIN IS-SOURCED
+sourced=0
+if [ -n "$ZSH_VERSION" ]; then
+ case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
+elif [ -n "$KSH_VERSION" ]; then
+ [ "$(cd -- "$(dirname -- "$0")" && pwd -P)/$(basename -- "$0")" != "$(cd -- "$(dirname -- "${.sh.file}")" && pwd -P)/$(basename -- "${.sh.file}")" ] && sourced=1
+elif [ -n "$BASH_VERSION" ]; then
+ (return 0 2>/dev/null) && sourced=1
+else # All other shells: examine $0 for known shell binary filenames.
+# Detects `sh` and `dash`; add additional shell filenames as needed.
+ case ${0##*/} in sh|-sh|dash|-dash) sourced=1;; esac
+fi
+# END IS-SOURCED
+
+# MAIN
+if test "${sourced}" = "0" ;
+then
+ action="${action:-${1:-NONE}}"
+ case "${action}" in
+ use_cep) use_cep ;;
+ use_ces) use_ces ;;
+ *) echo "Warning: action ${action} not defined yet. Skipping..." 1>&2 ; exit 1 ;
+ esac
+fi
bgstack15