Android and Hotspot 2.0/Passpoint

  • 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.

  • 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.

  • 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;
    }
}

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:
  1. Content has to be served from a web server with FQDN and HTTPS.
  2. Content has to be served with specified header settings.
  3. Content has to be base64 encoded.
  4. Content contains.
    1. Base64 encoded CA Certificate.
    2. Base64 encoded MgmtTree XML.
  5. These two are combined and base64 encoded as per step3.

  • 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 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.

  • 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.
  • technical/pp-android.txt
  • Last modified: 2025/07/07 20:17
  • by system