Android and Hotspot 2.0/Passpoint
Introduction
- This page will discuss the provisioning of Passpoint credentials to Android devices.
- Information from the following URL was used as a reference: https://source.android.com/docs/core/connect/wifi-passpoint
- Our approach, however will be a bit more hands on by looking at a PHP script that is used to provision a Passpoint profile.
Provision a Passpoint configuration file
- A Passpoint configuration file has to be provisioned through a web server that has a valid SSL certificate over HTTPS.
- If you look at the contents of the Passpoint configuration file it probably does not make much sense.
- It will be one long line of characters.
- This is because the file has to be served as base64 encoded content.
- Base64 encoding is a binary-to-text encoding scheme that represents binary data in an ASCII string format.
- It's primarily used to transmit binary data over channels that are designed to handle text-based data, such as email or HTTP.
- Essentially, it converts binary data into a string of printable characters (A-Z, a-z, 0-9, +, /) that are safe to transmit
- This is the general definition of base64 encoding.
- In our case, however, we are NOT transmitting binary data but rather other base64 encoded elements.
Simple CakePHP Controller
- The following file demonstrates how we can use PHP to serve the Passpoint configuration file.
- PasspointController.php
<?php namespace App\Controller; use App\Controller\AppController; use Cake\Http\Response; class PasspointController extends AppController{ protected $ca = <<<EOD -----BEGIN CERTIFICATE----- MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe 3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== -----END CERTIFICATE----- EOD; public function initialize():void{ parent::initialize(); $this->Authentication->allowUnauthenticated([ 'androidProfile']); } public function androidProfile(){ $response = $this->response; $response = $response->withHeader('Content-Transfer-Encoding', 'base64'); $response = $response->withType('application/x-wifi-config'); $home_sp = <<<EOD <MgmtTree xmlns="syncml:dmddf1.2"> <VerDTD>1.2</VerDTD> <Node> <NodeName>PerProviderSubscription</NodeName> <RTProperties> <Type> <DDFName>urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0</DDFName> </Type> </RTProperties> <Node> <NodeName>i001</NodeName> <Node> <NodeName>HomeSP</NodeName> <Node> <NodeName>FriendlyName</NodeName> <Value>RADIUSdesk-HS2.0 07-Jul-25</Value> </Node> <Node> <NodeName>FQDN</NodeName> <Value>mesh-manager.com</Value> </Node> </Node> <Node> <NodeName>Credential</NodeName> <Node> <NodeName>Realm</NodeName> <Value>mesh-manager.com</Value> </Node> <Node> <NodeName>UsernamePassword</NodeName> <Node> <NodeName>Username</NodeName> <Value>ppsk_demo@ppsk_demo</Value> </Node> <Node> <NodeName>Password</NodeName> <Value>dGVzdGluZzEyMw==</Value> </Node> <Node> <NodeName>EAPMethod</NodeName> <Node> <NodeName>EAPType</NodeName> <Value>21</Value> </Node> <Node> <NodeName>InnerMethod</NodeName> <Value>MS-CHAP-V2</Value> </Node> </Node> </Node> </Node> </Node> </Node> </MgmtTree> EOD; $home_sp_64 = base64_encode($home_sp); $ca_64 = base64_encode($this->ca); $home_sp_ca = <<<EOD MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="XXXXboundary" Content-Transfer-Encoding: base64 --XXXXboundary Content-Type: application/x-x509-ca-cert Content-Transfer-Encoding: base64 $ca_64 --XXXXboundary Content-Type: application/x-passpoint-profile Content-Transfer-Encoding: base64 $home_sp_64 --XXXXboundary-- EOD; $response = $response->withStringBody(base64_encode($home_sp_ca)); return $response; } }
Looking at the PHP File and what it does
Sets HTTP headers
- This is so that an Android device knows the content it is about to receive can potentially be a Passpoint profile.
$response = $response->withHeader('Content-Transfer-Encoding', 'base64'); $response = $response->withType('application/x-wifi-config');
- There are two items embedded.
- The one is the CA Certificate
- The other is a MgmtTree XML block which is the actual Passpoint profile definition
Embedded CA Certificate
- The CA have to be included.
protected $ca = <<<EOD -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- EOD;
- It will be also base64 encoded before it is combined with the other elements
$ca_64 = base64_encode($this->ca);
Embedded MgmtTree XML bloc
- The MgmtTree XML bloc is defined here:
$home_sp = <<<EOD <MgmtTree xmlns="syncml:dmddf1.2"> <VerDTD>1.2</VerDTD> <Node> ..... </Node> </MgmtTree> EOD;
- It will be also base64 encoded before it is combined with the other elements
$home_sp_64 = base64_encode($home_sp);
Combining and sending
- These two base64 encoded strings is then in turn combined and base64 eencoded one last time before it is served by the web server.
- Here we combine it:
$home_sp_ca = <<<EOD MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="XXXXboundary" Content-Transfer-Encoding: base64 --XXXXboundary Content-Type: application/x-x509-ca-cert Content-Transfer-Encoding: base64 $ca_64 --XXXXboundary Content-Type: application/x-passpoint-profile Content-Transfer-Encoding: base64 $home_sp_64 --XXXXboundary-- EOD;
- Here we serve it:
$response = $response->withStringBody(base64_encode($home_sp_ca)); return $response;
- Before we discuss the MgmtTree XML in more detail here is a short overview of the main points:
- Content has to be served from a web server with FQDN and HTTPS.
- Content has to be served with specified header settings.
- Content has to be base64 encoded.
- Content contains.
- Base64 encoded CA Certificate.
- Base64 encoded MgmtTree XML.
- These two are combined and base64 encoded as per step3.
MgmtTree XML
- The MgmtTree XML can be divided in two parts and when you do this it becomes much simpler to understand.
- The one part is the HomeSP.
- The other part is the Credential.
- There is also an optional third part called Extension which we also cover later.
- The HomeSP part is used by the Android to discover Hotspot 2.0 / Passpoint WiFi Access Points to connect to.
- When such an Access Point is found, the Credential part is used to try and authenticate the user.
- The HomeSP for all practical intend replaces the step where you would typically select or specify an SSID to connect to.
- There is one item however that “bleeds” from the HomeSP section to the Credential section and it can cause the authentication to fail. We will also cover this in detail on this page and what to do to fix things.
HomeSP
- HomeSP have to contain the following nodes:
- FriendlyName Displayed by the Android installer and also when connected to the Hotspot 2.0 / Passpoint Access Point.
- FQDN This translates to the value of Domain and or NAI Realm in Hostapd / OpenWrt. (This is the part that you have to be careful with)
- HomeSP can contain the following node:
- RoamingConsortiumOI This is the RCOI and one can specify multiple values separated by commas.
Credential
- The credential section are mostly straight forward.
- There are however some items to highlight.
Realm
- This Realm is used in EAP authentication and is RADIUS related.
- This realm has nothing to do with the NAI Realm (or Domain) in Hotspot 2.0. (HomeSP Section)
- It might be the same value of the NAI Realm but it is not a requirement.
- When the authentication request to RADIUS starts, an anonymous identity is used.
- This is also referred to as the Outer Identity.
- The convention Android uses is to formulate a username anonymouns@<value of realm>.
- In this case it will be anonymous@mesh-manager.com.
- The EAP protocol uses this recommended convention in order to determine the destination of RADIUS proxy requests.
Password
- The value of the password is also base64 encoded.
- Remember that encoded does not equal to encrypted.
- It is thus very easy to get the cleartext value of the password:
echo "dGVzdGluZzEyMw==" | base64 -d
- The get the base64 value of the password in turn you can use echo with the -n switch.
echo -n "testing123" | base64
EAPType
- EAPType will in most cases be 21.
- This is the number that is assigned for EAP/TTLS and part of the UsernamePassword detail.
- With this you also have to specify the InnerMethod to use when you are authenticating and can be one of the following.
- PAP
- CHAP
- MS-CHAP
- or MS-CHAP-V2
- The popular ones are MS-CHAP-V2 and PAP.
- When the RADIUS server uses Active Directory, it is the best to choose MS-CHAP-V2 in order to avoid compatibility issues.
Other Credential Options
- There are other credential options which is good to take note of although they beyond the scope of this discussion.
- SIM Authentication. This will typically require collaboration with a mobile company to validate the incoming RADIUS request against their database for authentication.
- DigitalCertificate Authentication. This require PKI infrastructure and the management of the client certificates.
Extension - Certificate FQDN Check (Domain Suffix Match)
- In WPA Supplicant we have the following option: domain_suffix_match
- If this is specified then wpa_supplicant will make sure that when the client authenticates to RADIUS that the domain name of the certificate used with EAP matches one of the specified values.
- If not it will reject the authentication.
- This is to protect against an Evil Twin scenario.
- With the Android Hotspot 2.0 setup it will take the value of FQDN under the HomeSP section as the value value for domain_suffix_match.
- This is not always the case in real life. Sometimes the certificate RADIUS used has another domain/FQDN.
- If you want to specify a different domain there is an Extension section.
<Node> <NodeName>Extension</NodeName> <Node> <NodeName>Android</NodeName> <Node> <NodeName>AAAServerTrustedNames</NodeName> <Node> <NodeName>FQDN</NodeName> <Value>radiusdesk.com;openwrt.org</Value> </Node> </Node> </Node> </Node>
- This section has to be on the same level as HomeSP and Credential.