Last active
January 9, 2017 18:58
-
-
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
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 | |
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