Created
January 12, 2017 05:18
-
-
Save gjyoung1974/eed14980962b726eb25efadd59882a21 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
#==================================================================================== | |
# Name : certenroll_mac.sh | |
# Author : Gordon Young, [email protected] | |
# Version : .01 | |
# Copyright : 2016 Gordon Young | |
# Description : A script to enroll a Mac OSX 10.11 device for a machine certificate | |
# : via a Microsoft Certificate Services CA | |
# Reference : This script supports the requirements in | |
# : XVZ Co > Information Security: OS Hardening Baseline Requirements .. | |
# : v--.--.-- | |
#==================================================================================== | |
# Script to enroll an OS X system via an AD CA's web enrollment. | |
# OS Version in preparation of bifurcating handling 802.1X profiles. | |
OS_VER=`sw_vers | awk '/ProductVersion/ { print $2 }'` | |
# The URL of the CA web enrollment page. | |
# Would be great to find a way to programmatically find this out... | |
# Ensure this is a FQDN, to make Kerberos work better | |
CA_URL="http://dc01.corp.acme.local/certsrv" | |
AD_DOMAIN="corp.acme.local" | |
# Certificate template to be used. | |
CERT_TYPE_MACHINE="ACMEOffline8021X" | |
CERT_TYPE_USER="ACMEUser" | |
CERT_TO_INSTALL="EMPTY" | |
## Create a temp directory and then use that for staging. | |
CWD=`pwd` | |
DATE_HASH=`date | openssl sha1` | |
TEMP_DIR=$CWD/$DATE_HASH | |
mkdir $TEMP_DIR | |
KEY="${TEMP_DIR}/autoenroll.key" | |
CSR="${TEMP_DIR}/autoenroll.csr" | |
DER="${TEMP_DIR}/autoenroll.der" | |
PEM="${TEMP_DIR}/autoenroll.pem" | |
PK12="${TEMP_DIR}/autoenroll.p12" | |
CA_CERT="${TEMP_DIR}/autoenroll-ca.crt" | |
OPENSSL_CONF="${TEMP_DIR}/openssl.conf" | |
MY1xCONF="${TEMP_DIR}/1xConf.networkConnect" | |
## Defaults | |
SERVER_STYLE="2k3" | |
CERT_STYLE="MACHINE" | |
NETWORK_SERVICE="Airport" | |
PROFILE_PATH= | |
SECURE_IMPORT="NO" | |
EVAL_CERT="NO" | |
## Get any supplied options. | |
while getopts k:c:p:l:m:t:n:s:u:i:exh? SWITCH | |
do | |
case $SWITCH in | |
k) KEYCHAIN_PATH=$OPTARG;; | |
c) CERT_TEMP=$OPTARG;; | |
p) PROFILE_PATH=$OPTARG;; | |
s) NETWORK_SERVICE=$OPTARG;; | |
l) CA_URL=$OPTARG;; | |
m) CERT_TYPE_MACHINE=$OPTARG;; | |
t) CERT_TYPE_USER=$OPTARG;; | |
u) CERT_STYLE=${OPTARG:="USER"};; | |
i) CERT_TO_INSTALL=$OPTARG;; | |
x) SECURE_IMPORT="YES";; | |
e) EVAL_CERT="YES";; | |
?) USAGE=YES;; | |
h) USAGE=YES;; | |
*) USAGE=YES;; | |
esac | |
done | |
# Functions | |
## Check to ensure wer're bound to AD and exit if not. | |
check_bind_and_name() { | |
BIND_STATUS=$(dsconfigad -show | sed -n 2p) | |
if [ "$BIND_STATUS" != "Active Directory Domain = $AD_DOMAIN" ]; then | |
echo "You aren't bound to AD!" | |
exit 78; # EX_CONFIG | |
fi | |
# We're bound, so get the machine name from AD. | |
DOMAIN_NAME=`/usr/sbin/dsconfigad -show | grep "Active Directory Domain" | awk '{ print $5 }'` | |
MACHINE_NAME=`/usr/sbin/dsconfigad -show | grep "Computer Account" | awk '{ print $4 }'` | |
} | |
## Check to ensure we have a user TGT | |
check_tgt() { | |
### CHANGE ME - ADD LOGIC TO ENSURE NON-ZERO result | |
MY_TGT_NAME=`klist | grep Principal | sed -n 1p | cut -d ':' -f 2 | cut -d ' ' -f 2` | |
} | |
## Generate csr with openssl for a machine | |
generate_csr_machine() { | |
/usr/bin/openssl req -new -batch -newkey rsa:2048 -nodes -keyout "${KEY}" -out "${CSR}" -subj "/CN=${MACHINE_NAME}.${DOMAIN_NAME}" | |
} | |
## Generate csr with openssl for a user | |
generate_csr_user() { | |
/usr/bin/openssl req -new -batch -newkey rsa:2048 -nodes -keyout "${KEY}" -out "${CSR}" -subj "/CN=${MY_TGT_NAME}" | |
} | |
## Get TGT via kinit - If 2k3, use password method if 2k8 | |
get_tgt_kinit() { | |
# /usr/bin/kinit -k ${MACHINE_NAME}$ | |
/usr/bin/kinit | |
/usr/bin/kinit -k ${MACHINE_NAME}$ | |
} | |
## Get TGT via password | |
get_tgt_password() { | |
AD_PASS=$(defaults read /Library/Preferences/DirectoryService/ActiveDirectory "AD Computer Password" | xxd -r -p) | |
expect <<EOF | |
spawn /usr/bin/kinit ${MACHINE_NAME}$ | |
expect "Please enter the password" | |
send -- "${AD_PASS}\r" | |
expect eof | |
EOF | |
} | |
## curl the csr to the CA | |
curl_csr() { | |
# First we do some really really ugly-looking awk work to url-encode the csr | |
# Later versions of curl do this for us... but we don't have that luxury. | |
ENCODED_CSR=`cat ${CSR} | hexdump -v -e '1/1 "%02x\t"' -e '1/1 "%_c\n"' | | |
LANG=C awk ' | |
$1 == "20" { printf("%s", "+"); next } | |
$2 ~ /^[a-zA-Z0-9.*()\/-]$/ { printf("%s", $2); next } | |
{ printf("%%%s", $1) }'` | |
# Now to post this to the Web Enrollment page. | |
# We'll need to capture the ReqID when it finishes. If it's a 2k8 domain, we just don't use it. | |
# New method 11-03-2009 | |
REQ_ID=`curl --negotiate -u : -d CertRequest=${ENCODED_CSR} -d SaveCert=yes -d Mode=newreq -d CertAttrib=CertificateTemplate:"${CERT_TYPE}" ${CA_URL}/certfnsh.asp | grep ReqID | sed -e 's/.*ReqID=\(.*\)&.*/\1/g'` | |
echo REQ_ID is ${REQ_ID} | |
# Verify if we actually have a request ID - if not, we need to bail. | |
if [ ! "$REQ_ID" ]; then | |
echo "WARNING: I didn't receive a request ID from the Certificate Authority." | |
echo "This likely means the certificate request failed - please contact your administrator." | |
exit 69; #EX_UNAVAILABLE | |
fi | |
} | |
## collect the issued cert | |
curl_cert() { | |
#NOTE: curl accepts whatever cert the server is using, to make it more likely to work in different | |
# environments. Also this pulls the cert down in PEM format, which needs to be converted into a | |
# PKCS12 file and then imported into the keychain. | |
## | |
#echo CRT is ${PEM}, CA_URL is ${CA_URL} | |
echo curl -k -o ${PEM} --negotiate -u : "${CA_URL}/certnew.cer?ReqID=${REQ_ID}&Enc=b64" | |
# `curl -k -o ${PEM} -A "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5" --negotiate -u : ${CA_URL}/certnew.cer?ReqID=${REQ_ID}&Enc=b64` | |
`curl -k -o ${PEM} --negotiate -u : ${CA_URL}/certnew.cer?ReqID=${REQ_ID}&Enc=b64` | |
} | |
eval_cert() { | |
# Find out if cert is trusted | |
if [ ! "`security verify-cert -c ${PEM} | grep "successful"`" ]; then | |
CA_URL=`openssl x509 -in ${PEM} -text | grep "CA Issuers - URI:http://" | awk '{ print $4 }' | sed 's/URI://'` | |
curl -o ${CA_CERT} ${CA_URL} | |
## I currently limit this to just the X.509 basic policy, else all policies are trusted. | |
/usr/bin/security add-trusted-cert -d -p basic -k ${KEYCHAIN_PATH} ${CA_CERT} | |
fi | |
} | |
add_cert() { | |
# General tool for adding in certs by hand if so desired. | |
if [ -f ${CERT_TO_INSTALL} ]; then | |
/usr/bin/security add-trusted-cert -d -p eap -k ${KEYCHAIN_PATH} ${CERT_TO_INSTALL} | |
else | |
echo "WARNING: ${CERT_TO_INSTALL} doens't exist." | |
fi | |
} | |
## Pack the cert up and import it ito the keyhcain | |
pack_and_import() { | |
## Build the cert and private key into a PKCS12 | |
openssl pkcs12 -export -in ${PEM} -inkey ${KEY} -out ${PK12} -name "$MACHINE_NAME" -passout pass:pass | |
if [ "SECURE_IMPORT" = "YES" ]; then | |
/usr/bin/security -x import ${PK12} -k ${KEYCHAIN_PATH} -f pkcs12 -P pass | |
else | |
/usr/bin/security import ${PK12} -k ${KEYCHAIN_PATH} -f pkcs12 -P pass | |
fi | |
} | |
## Import 802.1X profile and associate TLS identity with it | |
import_and_associate_machine(){ | |
networksetup -import8021xProfiles ${NETWORK_SERVICE} ${PROFILE_PATH} | |
networksetup -settlsidentityonsystemprofile ${NETWORK_SERVICE} ${PK12} pass | |
} | |
## Import 802.1X profile and associate TLS identity with it | |
import_and_associate_user(){ | |
networksetup -import8021xProfiles ${NETWORK_SERVICE} ${PROFILE_PATH} | |
networksetup -settlsidentityonuserprofile ${NETWORK_SERVICE} ${PK12} pass | |
} | |
## If you have particular configuration needs when generating the CSR you can build and use an OpenSSL config file here | |
build_openssl.conf(){ | |
echo > ${OPENSSL_CONF} " [ req ] | |
default_bits = 2048 | |
default_md = sha1 | |
#default_keyfile = key.pem | |
distinguished_name = req_distinguished_name | |
prompt = no | |
string_mask = nombstr | |
req_extensions = v3_req | |
[ req_distinguished_name ] | |
countryName = US | |
stateOrProvinceName = CA | |
organizationName = Your Company Here | |
organizationalUnitName = ITS | |
commonName = ${MACHINE_NAME}.${DOMAIN_NAME} | |
[ v3_req ] | |
basicConstraints = CA:FALSE | |
keyUsage = nonRepudiation, digitalSignature, keyEncipherment | |
subjectAltName = @alt_names | |
[ alt_names ] | |
DNS.1 = ${MACHINE_NAME}.${DOMAIN_NAME}" | |
} | |
## You can put your 1x profile in here after exporting it from System Preferences or using networksetup | |
build_1xconfig(){ | |
echo > ${MY1xCONF} " <?xml version="1.0" encoding="UTF-8"?> | |
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |
<plist version="1.0"> | |
<dict> | |
<key>8021X</key> | |
<dict> | |
<key>SystemProfiles</key> | |
<array> | |
<dict> | |
<key>EAPClientConfiguration</key> | |
<dict> | |
<key>AcceptEAPTypes</key> | |
<array> | |
<integer>13</integer> | |
</array> | |
<key>UserName</key> | |
<string>host/${MACHINE_NAME}.${DOMAIN_NAME}</string> | |
<key>Wireless Network</key> | |
<string>YourSSIDHere</string> | |
</dict> | |
<key>UserDefinedName</key> | |
<string>System Profile</string> | |
</dict> | |
</array> | |
</dict> | |
</dict> | |
</plist>" | |
} | |
usage(){ | |
SCRIPT=`basename $0` | |
cat <<EOF | |
Summary: | |
Manages identities and 802.1X profile for EAP-TLS authentication. | |
This script only works with a Microsoft Certificate Services CA. | |
Usage: | |
$SCRIPT [ -? ] | |
-k <PATH> : Path to the target keychain. | |
The default is the system keychain for MACHINE identities | |
and the user's default keychain for USER identities. | |
-c <PATH> : TBD | |
-p <PATH> : Path to the 802.1X profile to be imported. | |
-s <Interface> : Target network interface for the 802.1X profile. | |
The default is Airport. | |
-l <URL> : URL to the target Certificate Authority. | |
NOTE: You must use HTTP vs. HTTPS unless the CA's | |
root certificate is already trusted. | |
-m <TEMPLATE> : Cert template to use for machine identity requests. | |
-t <TEMPLATE> : Cert template to use for user identity requests. | |
-i <PATH> : RADIUS cert to install into the System Keychain with | |
the EAP trust policy. This is optional. | |
-u <STYLE> : Defines the identity type to request, MACHINE or USER. | |
The default is MACHINE. | |
-x <YES|NO> : Prevent the identity's private key from being exported | |
from the keychain. The default is "YES". | |
-e <YES|NO> : Flags whether to evalute if the identitiy is trusted | |
or not. If not the script will pull down the CA's | |
root cert and set full trust. The default is "NO". | |
e.g. | |
$ $SCRIPT -m "Computer Template" -l "http://ca.mydomain.lan/certsrv" | |
Add a RADIUS server certificate to the System keychain with EAP trust settings: | |
$ $SCRIPT -i ~/Desktop/myradius_server.crt | |
EOF | |
} | |
## Actual script here: | |
if [ "$USAGE" == "YES" ]; then | |
usage | |
exit 0 | |
fi | |
if [ "${CERT_TO_INSTALL}" != "EMPTY" ]; then | |
## I assume you only want to add the cert and exit afterwards. | |
add_cert | |
exit 0 | |
fi | |
if [ "$CERT_STYLE" = "MACHINE" ]; then | |
# Set keychain to the system keychain if not otherwise specified | |
[ "${KEYCHAIN_PATH}" = "" ] && KEYCHAIN_PATH="/Library/Keychains/System.keychain" | |
check_bind_and_name | |
generate_csr_machine | |
# TPH - 07-21-2009 | |
CERT_TYPE=${CERT_TYPE_MACHINE} | |
get_tgt_kinit | |
curl_csr | |
curl_cert | |
[ "${EVAL_CERT}" == "YES" ] && eval_cert | |
[ -n "${PROFILE_PATH}" ] && import_and_associate_machine | |
else | |
check_tgt | |
generate_csr_user | |
CERT_TYPE=${CERT_TYPE_USER} | |
curl_csr | |
curl_cert | |
# set the keychain to the default user keychain if not otherwise specified | |
[ "${KEYCHAIN_PATH}" = "" ] && KEYCHAIN_PATH=`security default-keychain | sed -e 's/"//g'` | |
[ "${EVAL_CERT}" == "YES" ] && eval_cert | |
[ -n "${PROFILE_PATH}" ] && import_and_associate_user | |
fi | |
pack_and_import | |
#TODO fix this | |
srm -r -f ${TEMP_DIR} | |
## EOF |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment