Skip to content

Instantly share code, notes, and snippets.

@jdalegonzalez
Last active January 9, 2017 18:58
Show Gist options
  • Save jdalegonzalez/34b203747d626d525f77ff992c651534 to your computer and use it in GitHub Desktop.
Save jdalegonzalez/34b203747d626d525f77ff992c651534 to your computer and use it in GitHub Desktop.
Script to create the necessary client components for a strongswan vpn user including a client private key, client public key, client certificate, and osx mobileprofile. Automates the instructions provided at https://raymii.org/s/tutorials/IPSEC_vpn_with_Ubuntu_15.04.html and tested on a Digital Ocean Ubuntu instance. YMMV
#!/bin/bash
set -e
#
# Script taken in large part from the instructions provided here:
#
# https://raymii.org/s/tutorials/IPSEC_vpn_with_Ubuntu_15.04.html
#
# Thanks Remy van Elst. I appreciate the help.
#
# This script uses pwgen as a part of the passkey for the user's p12 file.
# ( The format is ${passkeyPrefix}{Thing generated by pwgen}${passkeySuffix} )
# You either need to find the line that does that and change it to do something
# else or you need to install pwgen on your system (apt-get install pwgen worked for me)
#
# Written while at Axio, Inc - www.axio.com.
# Script is provided on an "AS IS" basis.
#
# AXIO MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
# IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, AXIO MAKES NO AND
# DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
# FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THIS SCRIPT WILL NOT
# INFRINGE ANY THIRD PARTY RIGHTS.
#
# AXIO SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THIS SCRIPT
# FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
# A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING IT,
# OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
# (But you knew that, right?)
#
# Change these settings to match your environment
ipsecDir="/etc/ipsec.d" # Location where the certs and keys go
caFileRoot="strongswanCert" # The name without extension of your CA cert
caCert="${caFileRoot}.der" # The filename of your CA cert
# (expected to live in /etc/ipsec.d/cacerts)
caPem="${caFileRoot}.pem" # The name for the pem version of the CA cert
# (this script will create if missing)
caPrivateKey="strongswanKey.der" # The filename for the private key of the CA
vpnHostCert="vpnHostCert.der" # The filename for the host's certificate.
# (expected to live in /etc/ipsec.d/certs)
passkeyPrefix="" # The first part of the passkey for the generated .p12. Can be ""
# or passed in as arg2
passkeySuffix="" # The last part of the passkey for the generated .p12. Can be ""
# or passed in as arg3
domain="" # The default. If username as an "@", what follows will become the
# domain
#
remoteIdentifier="" # Used in the client config. Must match a leftid in the ipsec.conf
# AND either be the subject of the server cert or one of its subject
# alternatives.
# Leave blank and the script will try to guess it from ipsec.conf
#
# A little bit of argument checking to get started
#
if [ $# -lt 1 ]; then
echo "Usage: $0 username [passkey_prefix] [passey_suffix]">&2
exit 1
fi
# If we've got an @ in the username, we'll split it into user and domain pieces
arr=(${1//@/ })
username=${arr[0]}
[[ "${arr[1]}" != "" ]] && domain=${arr[1]}
re='^[a-zA-Z0-9]+$'
if ! [[ "${username}" =~ ${re} ]]; then
echo "Invalid username: '${username}'. Username should only include letters and numbers">&2
exit 2
fi
if [[ "${domain}" == "" ]]; then
echo "This script needs a domain name to continue. Set a default in the script or pass the username in with @domain">&2
exit 2
fi
#
# Grab the extra arguments if they're there.
#
[ $# -gt 1 ] && passkeyPrefix=$2
[ $# -gt 2 ] && passkeySuffix=$3
#
# Save ourselves from having to full-path everything..
#
cd ${ipsecDir}
#
# Calculate all of the variables that can reasonably be calculated.
#
# You may have your own method for calculating these things, so feel
# free to change them.
#
ipaddress=$(hostname -I | cut -d' ' -f1)
passkey="${passkeyPrefix}$(pwgen -BcH ./cacerts/${caCert}#${username})${passkeySuffix}"
authorityCommonName=$( \
ipsec pki --print --in cacerts/${caCert} | \
awk -F", CN=" ' /^subject: / { print substr($2, 1, length($2)-1) } ' \
)
organization=$( \
ipsec pki --print --in cacerts/${caCert} | \
awk -F", O=" ' /^subject: / { sub(/, CN=.*$/, "", $2); print $2 } ' \
)
[ "${remoteIdentifier}" == "" ] && remoteIdentifier=$( \
cat ../ipsec.conf | awk 'BEGIN {FS="leftid="} /^ *leftid=/ { print $2 } ' \
)
# From doc I read here: https://wiki.strongswan.org/issues/1233
#
# Unless Apple fixed it by now, iOS does not support DNs as identities
# (as documented on AppleIKEv2Profile). So far such identities have not
# been transmitted as ASN.1 encoded DNs but as plain strings with type FQDN,
# so strongSwan is not able to match that to a config that has a DN as leftid.
#
# So, even though we'll check the remote identifier to see if it matches
# the subject DN, we're not going to count that as "found" and instead look
# for a match in the alternative names
#
testSubject=false
dn=$(\
openssl x509 -subject -inform DER -noout -in certs/${vpnHostCert} | \
sed -e "s~\s*subject=\s*/~~g" -e "s~/~, ~g"
)
found=false
if [ $testSubject = true -a "${dn}" != "" -a "${dn}" == "${remoteIdentifier}" ];then
found=true
fi
if [ $found = false ]; then
list=$(\
openssl x509 -inform DER -in certs/vpnHostCert.der -text -noout \
-certopt no_header,no_version,no_serial,no_signame,no_validity,no_subject,no_issuer,no_pubkey,no_sigdump,no_aux | \
awk '/X509v3 Subject Alternative Name:/ { getline; print }'
)
set -f; IFS=,
for i in $list; do
altName=$( echo $i | awk -F':' ' { print $2 }')
if [ "{$remoteIdentifier}" != "" -a "${remoteIdentifier}" == "${altName}" ]; then
found=true
break
fi
done
set =f; unset IFS
fi
if [ ${found} = false ]; then
extra=""
if ${testSubject}; then
extra="either the subject distinquised name or "
fi
echo "I couldn't find your remoteIdentifier: ${remoteIdentifier} in ${extra}one of the aternative names">&2
echo "Not generating any certificates">&2
exit 3
fi
#
# On to the work of actually creating the certificate for the user.
#
# The VPN client will need the user's private key, the corresponding client
# certificate, and the signing CA certificate. The The most convenient way is
# to put everything in a single signed PKCS#12 file and export it with a paraphrase.
#
# So, we need a PEM version of the CA certificate.
#
# Strictly speaking this could be a part of server setup, rather than a part of
# adding users but ... I put it here.
#
if [ ! -f cacerts/${caPem} ]; then
openssl x509 -inform DER -in cacerts/${caCert} -out cacerts/${caPem} -outform PEM
fi
#
# We create a keypair for our user.
#
# Private Key:
#
if [ ! -f private/${username}.key.der ]; then
ipsec pki --gen --type rsa --size 2048 --outform der > private/${username}.key.der
chmod 600 private/${username}.key.der
fi
#
# Public key, signed by our root ca we generated:
#
if [ ! -f certs/${username}.cert.der ]; then
ipsec pki --pub --in private/${username}.key.der --type rsa | \
ipsec pki --issue --lifetime 730 --cacert cacerts/${caCert} --cakey private/${caPrivateKey} \
--dn "C=NL, O=${organization}, CN=${username}@${domain}" \
--san "${username}" \
--san "${username}@${domain}" \
--san "${username}@${ipaddress}" \
--outform der > certs/${username}.cert.der
fi
if [ ! -f private/${username}.key.pem ]; then
openssl rsa -inform DER -in private/${username}.key.der -out private/${username}.key.pem -outform PEM
fi
if [ ! -f certs/${username}.cert.pem ]; then
openssl x509 -inform DER -in certs/${username}.cert.der -out certs/${username}.cert.pem -outform PEM
fi
if [ ! -f p12/${username}.p12 ]; then
openssl pkcs12 -export -inkey private/${username}.key.pem -in certs/${username}.cert.pem \
-name "${username} VPN Certificate" -certfile cacerts/${caPem} \
-caname "${authorityCommonName}" -passout pass:${passkey} -out p12/${username}.p12
fi
#
# The idea is that you, the runner of this script, will provide the passkey to the user
# and the generated .p12 on separate "channels" ie - email one, text the other; text one,
# download the other over ssl; download one over ssl, put the other on a memory stick; whatever
#
# So, I'm sticking the private key somewhere you can find it. You probably want to delete the
# file after you've delivered it.
#
if [ ! -f private/${username}.passkey ]; then
echo ${passkey} > private/${username}.passkey
fi
#
# Now, build an OSX mobile configuration file that we can hand-off to our end-user.
# If you're not using OSX folks, you may not need/want this step.
#
rootCAContent=$(base64 -w 52 ./cacerts/${caPem} | tr '\n' '\1')
rootCAContent=${rootCAContent::-1}
p12content=$(base64 -w 52 p12/${username}.p12 | tr '\n' '\1')
p12content=${p12content::-1}
if [ ! -d ./client_conf ]; then
mkdir ./client_conf
fi
if [ ! -d ./client_conf/osx.mobileconfig.template ]; then
cat > ./client_conf/osx.mobileconfig.template <<EOF
<?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>HasRemovalPasscode</key>
<false/>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadCertificateFileName</key>
<string>%ROOTCA_NAME%</string>
<key>PayloadContent</key>
<data>
%ROOTCA_CONTENT%
</data>
<key>PayloadDescription</key>
<string>Adds a CA root certificate</string>
<key>PayloadDisplayName</key>
<string>%AUTHORITY_COMMON_NAME%</string>
<key>PayloadIdentifier</key>
<string>com.apple.security.root.583A9C04-1760-44DB-8CF0-D17B83E08BD2</string>
<key>PayloadType</key>
<string>com.apple.security.root</string>
<key>PayloadUUID</key>
<string>583A9C04-1760-44DB-8CF0-D17B83E08BD2</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
<dict>
<key>PayloadCertificateFileName</key>
<string>%USERP12_NAME%</string>
<key>PayloadContent</key>
<data>
%USERP12_CONTENT%
</data>
<key>PayloadDescription</key>
<string>Adds a PKCS#12-formatted certificate</string>
<key>PayloadDisplayName</key>
<string>%USERP12_NAME%</string>
<key>PayloadIdentifier</key>
<string>com.apple.security.pkcs12.9944BE08-BAC9-4615-B373-A777D7EEF99B</string>
<key>PayloadType</key>
<string>com.apple.security.pkcs12</string>
<key>PayloadUUID</key>
<string>9944BE08-BAC9-4615-B373-A777D7EEF99B</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
<dict>
<key>IKEv2</key>
<dict>
<key>AuthenticationMethod</key>
<string>Certificate</string>
<key>ChildSecurityAssociationParameters</key>
<dict>
<key>DiffieHellmanGroup</key>
<integer>14</integer>
<key>EncryptionAlgorithm</key>
<string>AES-256</string>
<key>IntegrityAlgorithm</key>
<string>SHA2-256</string>
<key>LifeTimeInMinutes</key>
<integer>1440</integer>
</dict>
<key>DeadPeerDetectionRate</key>
<string>Medium</string>
<key>DisableMOBIKE</key>
<integer>0</integer>
<key>DisableRedirect</key>
<integer>0</integer>
<key>EnableCertificateRevocationCheck</key>
<integer>0</integer>
<key>EnablePFS</key>
<integer>0</integer>
<key>ExtendedAuthEnabled</key>
<false/>
<key>IKESecurityAssociationParameters</key>
<dict>
<key>DiffieHellmanGroup</key>
<integer>14</integer>
<key>EncryptionAlgorithm</key>
<string>AES-256</string>
<key>IntegrityAlgorithm</key>
<string>SHA2-256</string>
<key>LifeTimeInMinutes</key>
<integer>1440</integer>
</dict>
<key>LocalIdentifier</key>
<string>%USER_IDENTIFIER%</string>
<key>PayloadCertificateUUID</key>
<string>9944BE08-BAC9-4615-B373-A777D7EEF99B</string>
<key>RemoteAddress</key>
<string>%IP_ADDRESS%</string>
<key>RemoteIdentifier</key>
<string>%REMOTE_IDENTIFIER%</string>
<key>ServerCertificateCommonName</key>
<string>%REMOTE_IDENTIFIER%</string>
<key>ServerCertificateIssuerCommonName</key>
<string>%AUTHORITY_COMMON_NAME%</string>
<key>UseConfigurationAttributeInternalIPSubnet</key>
<integer>0</integer>
</dict>
<key>IPv4</key>
<dict>
<key>OverridePrimary</key>
<integer>1</integer>
</dict>
<key>PayloadDescription</key>
<string>Configures VPN settings</string>
<key>PayloadDisplayName</key>
<string>VPN</string>
<key>PayloadIdentifier</key>
<string>com.apple.vpn.managed.C97D6B11-2A83-42AF-AB04-3E440A98EC4A</string>
<key>PayloadType</key>
<string>com.apple.vpn.managed</string>
<key>PayloadUUID</key>
<string>050208AB-5261-4272-B9EA-E5BDAF0FF3F1</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>Proxies</key>
<dict>
<key>HTTPEnable</key>
<integer>0</integer>
<key>HTTPSEnable</key>
<integer>0</integer>
</dict>
<key>UserDefinedName</key>
<string>%ORGANIZATION% VPN</string>
<key>VPNType</key>
<string>IKEv2</string>
</dict>
</array>
<key>PayloadDisplayName</key>
<string>VPN Setup</string>
<key>PayloadIdentifier</key>
<string>%DOMAIN%.vpn.profile</string>
<key>PayloadOrganization</key>
<string>%ORGANIZATION%</string>
<key>PayloadRemovalDisallowed</key>
<false/>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>13983A73-5318-460A-8255-04AF6AAC192B</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>
EOF
fi
sed -e "s~%USERP12_NAME%~${username}.p12~" \
-e "s~%USERP12_CONTENT%~${p12content}~" \
-e "s~%ROOTCA_NAME%~${caFileRoot}~" \
-e "s~%ROOTCA_CONTENT%~${rootCAContent}~" \
-e "s~%USER_IDENTIFIER%~${username}@${domain}~" \
-e "s~%IP_ADDRESS%~${ipaddress}~" \
-e "s~%REMOTE_IDENTIFIER%~${remoteIdentifier}~" \
-e "s~%AUTHORITY_COMMON_NAME%~${authorityCommonName}~" \
-e "s~%ORGANIZATION%~${organization}~" \
-e "s~%DOMAIN%~${domain}~" \
client_conf/osx.mobileconfig.template > client_conf/${username}.vpn.tmp
sed -e "s/\d1/\n /g" client_conf/${username}.vpn.tmp > client_conf/${username}.vpn.mobileconfig
rm client_conf/${username}.vpn.tmp
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment