Tim's blah blah blah

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:

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>

#Security