On-demand iOS VPN using Configuration Profiles
(Updated: )
After setting up an IKEv2 VPN on my raspberry-pi (vanwerkhoven.org), I wanted my iPhone to connect to it automatically and on-demand.
Update: with WireGuard this is much simpler, if your setup supports this I’d recommend this over IPsec configuration hell.
Using this strongswan guide (strongswan.org) and Apple’s configuration profile reference (apple.com), I configured two VPN profiles to use on my iOS devices: a basic one and an on-demand connecting profile.
The simple profile simply adds a VPN configuration you can turn on and off as you wish. For the on-demand VPN, I have these requirements:
- When on home wifi, don’t use VPN. Check by SSID
- When on other wifi, enforce VPN
- Else, leave up to user (e.g. on cellular) Be sure to read through and change FIXME entries to suit your needs
Simple profile ¶
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- Set the name to whatever you like, it is used in the profile list on the device -->
<key>PayloadDisplayName</key>
<string>FIXME: My Home VPN</string>
<!-- This is a reverse-DNS style unique identifier used to detect duplicate profiles -->
<key>PayloadIdentifier</key>
<string>FIXME: tld.domain.subdomain.vpn1</string>
<!-- A globally unique identifier, use uuidgen on Linux/Mac OS X to generate it -->
<key>PayloadUUID</key>
<string>FIXME: 0BE3E9A7-52E1-4F99-A9FF-6CFADA6A467F</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadContent</key>
<array>
<!-- It is possible to add multiple VPN payloads with different identifiers/UUIDs and names -->
<dict>
<!-- This is an extension of the identifier given above -->
<key>PayloadIdentifier</key>
<string>FIXME: tld.domain.subdomain.vpn1.conf1</string>
<!-- A globally unique identifier for this payload -->
<key>PayloadUUID</key>
<string>FIXME: 88998E8A-6335-4AA8-94D7-3114E3DA1512</string>
<key>PayloadType</key>
<string>com.apple.vpn.managed</string>
<key>PayloadVersion</key>
<integer>1</integer>
<!-- This is the name of the VPN connection as seen in the VPN application later -->
<key>UserDefinedName</key>
<string>FIXME: My Home VPN</string>
<key>VPNType</key>
<string>IKEv2</string>
<key>IKEv2</key>
<dict>
<!-- Hostname or IP address of the VPN server -->
<key>RemoteAddress</key>
<string>FIXME: FQDN</string>
<!-- Remote identity, can be a FQDN, a userFQDN, an IP or (theoretically) a certificate's subject DN. Can't be empty.
IMPORTANT: DNs are currently not handled correctly, they are always sent as identities of type FQDN -->
<key>RemoteIdentifier</key>
<string>FIXME: FQDN</string>
<!-- Local IKE identity, same restrictions as above. If it is empty the client's IP address will be used -->
<key>LocalIdentifier</key>
<string></string>
<!-- Optional, if it matches the CN of the root CA certificate (not the full subject DN) a certificate request will be sent
NOTE: If this is not configured make sure to configure leftsendcert=always on the server, otherwise it won't send its certificate -->
<key>ServerCertificateIssuerCommonName</key>
<string></string>
<!-- Optional, the CN or one of the subjectAltNames of the server certificate to verify it, if not set RemoteIdentifier will be used -->
<key>ServerCertificateCommonName</key>
<string></string>
<!-- Set AuthenticationMethod to None to enable password based EAP -->
<key>AuthenticationMethod</key>
<string>None</string>
<!-- The client uses EAP to authenticate -->
<key>ExtendedAuthEnabled</key>
<integer>1</integer>
<!-- User name for EAP authentication. Since iOS 9 this is optional, the user is prompted when the profile is installed -->
<key>AuthName</key>
<string>FIXME: INSERTUSERNAME</string>
<!-- Optional password for EAP authentication, if it is not set the user is prompted when the profile is installed -->
<key>AuthPassword</key>
<string>FIXME: INSERTPASSWORD</string>
<!-- The next two dictionaries are optional (as are the keys in them), but it is recommended to specify them as the default is to use 3DES.
IMPORTANT: Because only one proposal is sent (even if nothing is configured here) it must match the server configuration -->
<key>IKESecurityAssociationParameters</key>
<dict>
<key>EncryptionAlgorithm</key> <!-- Can be AES-128, AES-256, AES-128-GCM, AES-256-GCM -->
<string>AES-128</string>
<key>IntegrityAlgorithm</key>
<string>SHA2-256</string> <!-- Can be SHA1-96, SHA1-160, SHA2-256, SHA2-384, SHA2-512 -->
<key>DiffieHellmanGroup</key>
<integer>19</integer> <!-- Can be 1, 2, 5, 14 (Default), 15, 16, 17, 18, 19, 20, or 21, see https://www.iana.org/assignments/ikev2-parameters/ikev2-parameters.xhtml#ikev2-parameters-8 and https://community.cisco.com/t5/security-documents/diffie-hellman-groups/ta-p/3147010-->
</dict>
<key>ChildSecurityAssociationParameters</key>
<dict>
<key>EncryptionAlgorithm</key>
<string>AES-128</string>
<key>IntegrityAlgorithm</key>
<string>SHA2-256</string>
<key>DiffieHellmanGroup</key>
<integer>19</integer>
</dict>
</dict>
</dict>
</array>
</dict>
</plist>
On-Demand profile ¶
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- Set the name to whatever you like, it is used in the profile list on the device -->
<key>PayloadDisplayName</key>
<string>FIXME: Home OnDemand VPN profile</string>
<!-- This is a reverse-DNS style unique identifier used to detect duplicate profiles -->
<key>PayloadIdentifier</key>
<string>FIXME: tld.domain.subdomain.vpn1</string>
<!-- A globally unique identifier, use uuidgen on Linux/Mac OS X to generate it -->
<key>PayloadUUID</key>
<string>FIXME: 0BE3E9A7-52E1-4F99-A9FF-6CFADA6A467F</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadContent</key>
<array>
<!-- It is possible to add multiple VPN payloads with different identifiers/UUIDs and names -->
<dict>
<!-- This is an extension of the identifier given above -->
<key>PayloadIdentifier</key>
<string>FIXME: tld.domain.subdomain.vpn1.conf1</string>
<!-- A globally unique identifier for this payload -->
<key>PayloadUUID</key>
<string>FIXME: 88998E8A-6335-4AA8-94D7-3114E3DA1512</string>
<key>PayloadType</key>
<string>com.apple.vpn.managed</string>
<key>PayloadVersion</key>
<integer>1</integer>
<!-- Display name of VPN connection settings -->
<key>PayloadDisplayName</key>
<string>FIXME: Home OnDemand VPN</string>
<!-- This is the name of the VPN connection as seen in the VPN application later, shown as 'service name' -->
<key>UserDefinedName</key>
<string>FIXME: Home OnDemand VPN</string>
<key>VPNType</key>
<string>IKEv2</string>
<key>IKEv2</key>
<dict>
<!-- Hostname or IP address of the VPN server -->
<key>RemoteAddress</key>
<string>FIXME: FQDN</string>
<!-- Remote identity, can be a FQDN, a userFQDN, an IP or (theoretically) a certificate's subject DN. Can't be empty.
IMPORTANT: DNs are currently not handled correctly, they are always sent as identities of type FQDN -->
<key>RemoteIdentifier</key>
<string>FIXME: FQDN</string>
<!-- Local IKE identity, same restrictions as above. If it is empty the client's IP address will be used -->
<key>LocalIdentifier</key>
<string></string>
<!-- Optional, if it matches the CN of the root CA certificate (not the full subject DN) a certificate request will be sent
NOTE: If this is not configured make sure to configure leftsendcert=always on the server, otherwise it won't send its certificate -->
<key>ServerCertificateIssuerCommonName</key>
<string></string>
<!-- Optional, the CN or one of the subjectAltNames of the server certificate to verify it, if not set RemoteIdentifier will be used -->
<key>ServerCertificateCommonName</key>
<string></string>
<!-- Set AuthenticationMethod to None to enable password based EAP -->
<key>AuthenticationMethod</key>
<string>None</string>
<!-- The client uses EAP to authenticate -->
<key>ExtendedAuthEnabled</key>
<integer>1</integer>
<!-- User name for EAP authentication. Since iOS 9 this is optional, the user is prompted when the profile is installed -->
<key>AuthName</key>
<string>FIXME: INSERTUSERNAME</string>
<!-- Optional password for EAP authentication, if it is not set the user is prompted when the profile is installed -->
<!-- Use random strings, e.g. like: dd if=/dev/urandom count=1 bs=32 2>/dev/null | base64 -->
<key>AuthPassword</key>
<string>FIXME: INSERTPASSWORD</string>
<!-- The next two dictionaries are optional (as are the keys in them), but it is recommended to specify them as the default is to use 3DES.
IMPORTANT: Because only one proposal is sent (even if nothing is configured here) it must match the server configuration -->
<key>IKESecurityAssociationParameters</key>
<dict>
<key>EncryptionAlgorithm</key> <!-- Can be AES-128, AES-256, AES-128-GCM, AES-256-GCM -->
<string>AES-128</string>
<key>IntegrityAlgorithm</key>
<string>SHA2-256</string> <!-- Can be SHA1-96, SHA1-160, SHA2-256, SHA2-384, SHA2-512 -->
<key>DiffieHellmanGroup</key>
<integer>19</integer> <!-- Can be 1, 2, 5, 14 (Default), 15, 16, 17, 18, 19, 20, or 21, see https://www.iana.org/assignments/ikev2-parameters/ikev2-parameters.xhtml#ikev2-parameters-8 and https://community.cisco.com/t5/security-documents/diffie-hellman-groups/ta-p/3147010-->
</dict>
<key>ChildSecurityAssociationParameters</key>
<dict>
<key>EncryptionAlgorithm</key>
<string>AES-128</string>
<key>IntegrityAlgorithm</key>
<string>SHA2-256</string>
<key>DiffieHellmanGroup</key>
<integer>19</integer>
</dict>
<key>OnDemandEnabled</key>
<integer>1</integer>
<key>OnDemandRules</key>
<array>
<!--
1. Check if we are connected to a WiFi network
2. Check if the SSID is included in the SSIDMatch-array
3. If 1 + 2 are true, then disconnect the tunnel
-->
<dict>
<key>InterfaceTypeMatch</key>
<string>WiFi</string>
<key>SSIDMatch</key>
<array>
<string>FIXME: HOMEWIFISSID</string>
</array>
<key>Action</key>
<string>Disconnect</string>
</dict>
<!--
Connect on all WiFi not home
-->
<dict>
<key>InterfaceTypeMatch</key>
<string>WiFi</string>
<key>Action</key>
<string>Connect</string>
</dict>
<!--
1. For each connection attempt, test if the domain name is included in the Domains-array
2. If 1 is true, try domain name resolution
3. If 2 fails or times out, establish a VPN connection
-->
<!--
<dict>
<key>Action</key>
<string>EvaluateConnection</string>
<key>ActionParameters</key>
<array>
<dict>
<key>Domains</key>
<array>
<string>*.internal.mydomain.com</string>
</array>
<key>DomainAction</key>
<string>ConnectIfNeeded</string>
</dict>
</array>
</dict>
-->
<!--
Default entry, ignore any other cases
-->
<dict>
<key>Action</key>
<string>Ignore</string>
</dict>
</array> <!-- End of OnDemandRules -->
</dict> <!-- End of IKEv2 -->
</dict>
<!-- This payload is optional but it provides an easy way to install the CA certificate together with the configuration -->
<dict>
<key>PayloadIdentifier</key>
<string>tld.domain.subdomain.vpn1.cert</string>
<key>PayloadUUID</key>
<string>8CF9A17F-EB30-45A4-A416-7121046879AD</string>
<key>PayloadType</key>
<string>com.apple.security.root</string>
<key>PayloadVersion</key>
<integer>1</integer>
<!-- This is the Base64 (PEM) encoded CA certificate -->
<!-- Get from cat /etc/ipsec.d/certs/vpn-server-cert.pem or similar -->
<key>PayloadContent</key>
<data>
MIIFaz...
</data>
</dict>
</array> <!-- End of array of configurations -->
</dict>
</plist>