Android and Hotspot 2.0/Passpoint
Introduction
This page will discuss the provisioning of Passpoint credentials to Android devices.
-
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
- 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
$response = $response->withHeader('Content-Transfer-Encoding', 'base64');
$response = $response->withType('application/x-wifi-config');
Embedded CA Certificate
protected $ca = <<<EOD
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
EOD;
$ca_64 = base64_encode($this->ca);
Embedded MgmtTree XML bloc
$home_sp = <<<EOD
<MgmtTree xmlns="syncml:dmddf1.2">
<VerDTD>1.2</VerDTD>
<Node>
.....
</Node>
</MgmtTree>
EOD;
$home_sp_64 = base64_encode($home_sp);
Combining and sending
$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;
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
Credential
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
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>