Passed
Push — master ( f7b675...62724b )
by Stefan
08:27
created

DevicePPOSUXML   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 243
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 17
eloc 60
c 3
b 0
f 0
dl 0
loc 243
rs 10

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A mimeChunkCaCerts() 0 12 2
A mimeChunkClientCert() 0 6 1
A mimeChunkPpsMo() 0 6 1
A homeSP() 0 29 3
A writeInstaller() 0 33 3
A credentialCreationDate() 0 5 1
A aaaServerTrustRoot() 0 16 2
A perProviderSubscription() 0 24 1
A credential() 0 25 1
A writeDeviceInfo() 0 4 1
1
<?php
2
3
/*
4
 * ******************************************************************************
5
 * Copyright 2011-2017 DANTE Ltd. and GÉANT on behalf of the GN3, GN3+, GN4-1 
6
 * and GN4-2 consortia
7
 *
8
 * License: see the web/copyright.php file in the file structure
9
 * ******************************************************************************
10
 */
11
12
/**
13
 * This file contains the TestModule class
14
 *
15
 * This is a very basic example of using the CAT API.  
16
 *
17
 * The module contains two files
18
 * in the Files directory. They will illustrate the use of the {@link DeviceConfig::copyFile()} method.
19
 * One fille will be coppied without the name change, for the second we will provide a new name.
20
 * The API also contains a similar {@link DeviceConfig::translateFile()} method, which is special to Windows installers and not used in this example.
21
 *
22
 * This module will collect all certificate files stored in the database for a given profile and will copy them to the working directory.
23
 *
24
 * If, for the given profile, an information file is available, this will also be copied to the working directory.
25
 *
26
 * The installer will collect all available configuration attributes and save them to a file in the form of the PHP print_r output.
27
 *
28
 * Finally, the installer will create a zip archive containing all above files and this file 
29
 * will be sent to the user as the configurator file.
30
 *
31
 * Go to the {@link DeviceTestModule} and {@link DeviceConfig} class definitions to learn more.
32
 *  
33
 * @package ModuleWriting
34
 */
35
36
namespace devices\PP_OSU_XML;
37
38
use Exception;
39
40
/**
41
 * This is the main implementation class of the module
42
 *
43
 * The name of the class must the the 'Device' followed by the name of the module file
44
 * (without the '.php' extension), so in this case the file is "TestModule.php" and
45
 * the class is DeviceTestModule.
46
 *
47
 * The class MUST define the constructor method and one additional 
48
 * public method: {@link writeInstaller()}.
49
 *
50
 * All other methods and properties should be private. This example sets zipInstaller method to protected, so that it can be seen in the documentation.
51
 *
52
 * It is important to understand how the device module fits into the whole picture, so here is s short descrption.
53
 * An external caller (for instance {@link GUI::generateInstaller()}) creates the module device instance and prepares
54
 * its environment for a given user profile by calling {@link DeviceConfig::setup()} method.
55
 *      this will:
56
 *       - create the temporary directory and save its path as $this->FPATH
57
 *       - process the CA certificates and store results in $this->attributes['internal:CAs'][0]
58
 *            $this->attributes['internal:CAs'][0] is an array of processed CA certificates
59
 *            a processed certifincate is an array 
60
 *               'pem' points to pem feromat certificate
61
 *               'der' points to der format certificate
62
 *               'md5' points to md5 fingerprint
63
 *               'sha1' points to sha1 fingerprint
64
 *               'name' points to the certificate subject
65
 *               'root' can be 1 for self-signed certificate or 0 otherwise
66
 *       - save the info_file (if exists) and put the name in $this->attributes['internal:info_file_name'][0]
67
 * Finally, the module {@link DeviceConfig::writeInstaller ()} is called and the returned path name is used for user download.
68
 *
69
 * @package ModuleWriting
70
 */
71
class DevicePPOSUXML extends \core\DeviceConfig {
72
73
    /**
74
     * Constructs a Device object.
75
     *
76
     * @final not to be redefined
77
     */
78
    final public function __construct() {
79
        parent::__construct();
80
        $this->setSupportedEapMethods([\core\common\EAP::EAPTYPE_SILVERBULLET]);
81
    }
82
83
    /**
84
     * creates a AAAServerTrustRoot XML fragment. Currently unused, not clear
85
     * if Android supports this.
86
     * 
87
     * @return string
88
     */
89
    private function aaaServerTrustRoot() {
0 ignored issues
show
Unused Code introduced by
The method aaaServerTrustRoot() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
90
91
        $retval = '<Node>
92
        <NodeName>AAAServerTrustRoot</NodeName>';
93
        foreach ($this->attributes['internal:CAs'][0] as $oneCert) {
94
            $retval .= '<Node>
95
                         <NodeName>' . $oneCert['uuid'] . '</NodeName>
96
                             <Node>
97
                               <NodeName>CertSHA256Fingerprint</NodeName>
98
                               <Value>' . $oneCert['sha256'] . '</Value>
99
                             </Node>
100
                       </Node>
101
                  ';
102
        }
103
        $retval .= '</Node>';
104
        return $retval;
105
    }
106
107
    /**
108
     * creates a CreationDate XML fragment for use in Credential. Currently
109
     * unused, not clear if Android supports this.
110
     * 
111
     * @return string
112
     */
113
    private function credentialCreationDate() {
0 ignored issues
show
Unused Code introduced by
The method credentialCreationDate() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
114
        $now = new \DateTime();
115
        return '<Node>
116
          <NodeName>CreationDate</NodeName>
117
          <Value>' . $now->format("Y-m-d") . "T" . $now->format("H:i:s") . "Z" . '</Value>
118
        </Node>';
119
    }
120
121
    /**
122
     * creates a HomeSP XML fragment for consortium identification.
123
     * 
124
     * @return string
125
     */
126
    private function homeSP() {
127
        $retval = '<Node>
128
        <NodeName>HomeSP</NodeName>
129
        <Node>
130
          <NodeName>FriendlyName</NodeName>
131
          <Value>' . sprintf(_("%s via Passpoint"), \config\ConfAssistant::CONSORTIUM['display_name']) . '</Value>
132
        </Node>
133
        <Node>
134
          <NodeName>FQDN</NodeName>
135
          <Value>' . $this->attributes['eap:server_name'][0] /* what, only one FQDN allowed? */ . '</Value>
136
        </Node>
137
        <Node>
138
          <NodeName>RoamingConsortiumOI</NodeName>
139
          <Value>';
140
        $oiList = "";
141
        $numberOfOi = count(\config\ConfAssistant::CONSORTIUM['interworking-consortium-oi']);
142
        foreach (\config\ConfAssistant::CONSORTIUM['interworking-consortium-oi'] as $index => $oneOi) {
143
            // according to spec, must be lowercase ASCII without dashes
144
            // but sample I got was all uppercase, so let's try with that
145
            $oiList .= str_replace("-", "", trim(strtoupper($oneOi)));
146
            if ($index < $numberOfOi - 1) {
147
                // according to spec, comma-separated
148
                $oiList .= ",";
149
            }
150
        }
151
        $retval .= $oiList . '</Value>
152
        </Node>
153
      </Node>';
154
        return $retval;
155
    }
156
157
    /**
158
     * creates a Credential XML fragment for client identification
159
     * 
160
     * @return string
161
     */
162
    private function credential() {
163
        $retval = '<Node>
164
        <NodeName>Credential</NodeName>';
165
        /* the example file I got did not include CreationDate, so omit it
166
         * 
167
         * $content .= $this->credentialCreationDate();
168
         */
169
        $retval .= '
170
          <Node>
171
            <NodeName>DigitalCertificate</NodeName>
172
            <Node>
173
              <NodeName>Realm</NodeName>
174
              <Value>' . $this->attributes['internal:realm'][0] . '</Value>
175
            </Node>
176
            <Node>
177
              <NodeName>CertificateType</NodeName>
178
              <Value>x509v3</Value>
179
            </Node>
180
            <Node>
181
              <NodeName>CertSHA256Fingerprint</NodeName>
182
              <Value>' . strtoupper($this->clientCert["sha256"]) /* the actual cert has to go... where? */ . '</Value>
183
            </Node>
184
          </Node>
185
      </Node>';
186
        return $retval;
187
    }
188
189
    /**
190
     * creates the overall perProviderSubscription XML
191
     * 
192
     * @return string
193
     */
194
    private function perProviderSubscription() {
195
        $retval = '<MgmtTree xmlns="syncml:dmddf1.2">
196
  <VerDTD>1.2</VerDTD>
197
  <Node>
198
    <NodeName>PerProviderSubscription</NodeName>
199
    <RTProperties>
200
      <Type>
201
        <DDFName>urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0</DDFName>
202
      </Type>
203
    </RTProperties>
204
    <Node>
205
      <NodeName>CATPasspointSetting</NodeName>';
206
        /* it seems that Android does NOT want the AAAServerTrustRoot section
207
          and instead always validates against the MIME cert attached
208
209
          $content .= $this->aaaServerTrustRoot();
210
         */
211
        $retval .= $this->homeSP();
212
        $retval .= $this->credential();
213
214
        $retval .= '</Node>
215
  </Node>
216
</MgmtTree>';
217
        return $retval;
218
    }
219
220
    /**
221
     * creates a MIME part containing the base64-encoded PPS-MO
222
     * 
223
     * @return string
224
     */
225
    private function mimeChunkPpsMo() {
226
        return '--{boundary}
227
Content-Type: application/x-passpoint-profile
228
Content-Transfer-Encoding: base64
229
230
' . chunk_split(base64_encode($this->perProviderSubscription()), 76, "\n");
231
    }
232
233
    /**
234
     * creates a MIME part containing the base64-encoded CA certs (PEM)
235
     * 
236
     * @return string
237
     */
238
    private function mimeChunkCaCerts() {
239
        $retval = '--{boundary}
240
Content-Type: application/x-x509-ca-cert
241
Content-Transfer-Encoding: base64
242
';
243
        // then, another PEM chunk for each CA certificate we referenced earlier
244
        // only leaves me to wonder what the "URL" for those is...
245
        // TODO: more than one CA is currently untested
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
246
        foreach ($this->attributes['internal:CAs'][0] as $oneCert) {
247
            $retval .= chunk_split(base64_encode($oneCert['pem']), 76, "\n");
248
        }
249
        return $retval;
250
    }
251
252
    /**
253
     * creates a MIME part containing the base64-encoded client cert PKCS#12
254
     * structure - no password.
255
     * 
256
     * @return string
257
     */
258
    private function mimeChunkClientCert() {
259
        return '--{boundary}
260
Content-Type: application/x-pkcs12
261
Content-Transfer-Encoding: base64
262
263
' . chunk_split(base64_encode($this->clientCert['certdataclear']), 76, "\n"); // is PKCS#12, with cleartext key
264
    }
265
    /**
266
     * prepare the PPS-MO file with cert MIME attachments
267
     *
268
     * @return string installer path name
269
     */
270
    public function writeInstaller() {
271
        $this->loggerInstance->debug(4, "HS20 PerProviderSubscription Managed Object Installer start\n");
272
        // sigh... we need to construct a MIME envelope for the payload and the cert data
273
        $content_encoded = 'Content-Type: multipart/mixed; boundary={boundary}
274
Content-Transfer-Encoding: base64
275
276
';
277
        $content_encoded .= $this->mimeChunkPpsMo();
278
        $content_encoded .= $this->mimeChunkCaCerts();
279
        $content_encoded .= $this->mimeChunkClientCert();
280
        // this was the last MIME chunk; end the file orderly
281
        $content_encoded .= "--{boundary}--\n";
282
        // strangely enough, now encode ALL OF THIS in base64 again. Whatever.
283
        file_put_contents('installer_profile', chunk_split(base64_encode($content_encoded), 76, "\n"));
284
285
        // $fileName = $this->installerBasename . '.bin';
286
        $fileName = "passpoint.config";
287
288
        if (!$this->sign) {
289
            rename("installer_profile", $fileName);
290
            return $fileName;
291
        }
292
293
        // still here? We are signing. That actually can't be - the spec doesn't
294
        // foresee signing.
295
        // but if they ever change their mind, we are prepared
296
297
        $outputFromSigning = system($this->sign . " installer_profile '$fileName' > /dev/null");
298
        if ($outputFromSigning === FALSE) {
299
            $this->loggerInstance->debug(2, "Signing the ONC installer $fileName FAILED!\n");
300
        }
301
302
        return $fileName;
303
    }
304
305
    /**
306
     * prepare module desctiption and usage information
307
     * 
308
     * @return string HTML text to be displayed in the information window
309
     */
310
    public function writeDeviceInfo() {
311
        $out = "<p>";
312
        $out .= _("This installer is an example only. It produces a zip file containig the IdP certificates, info and logo files (if such have been defined by the IdP administrator) and a dump of all available attributes.");
313
        return $out;
314
    }
315
316
}
317