Test Failed
Branch release_2_0 (1bcaa8)
by Stefan
07:08
created

MobileconfigSuperclass::passPointBlock()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 37
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 37
rs 9.7998
c 0
b 0
f 0
cc 4
nc 8
nop 1
1
<?php
2
/*
3
 * *****************************************************************************
4
 * Contributions to this work were made on behalf of the GÉANT project, a 
5
 * project that has received funding from the European Union’s Framework 
6
 * Programme 7 under Grant Agreements No. 238875 (GN3) and No. 605243 (GN3plus),
7
 * Horizon 2020 research and innovation programme under Grant Agreements No. 
8
 * 691567 (GN4-1) and No. 731122 (GN4-2).
9
 * On behalf of the aforementioned projects, GEANT Association is the sole owner
10
 * of the copyright in all material which was developed by a member of the GÉANT
11
 * project. GÉANT Vereniging (Association) is registered with the Chamber of 
12
 * Commerce in Amsterdam with registration number 40535155 and operates in the 
13
 * UK as a branch of GÉANT Vereniging.
14
 * 
15
 * Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands. 
16
 * UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK
17
 *
18
 * License: see the web/copyright.inc.php file in the file structure or
19
 *          <base_url>/copyright.php after deploying the software
20
 */
21
22
/**
23
 * This file contains the installer for iOS devices and Apple 10.7 Lion
24
 *
25
 *
26
 * @author Stefan Winter <[email protected]>
27
 * @package Developer
28
 */
29
30
namespace devices\apple_mobileconfig;
31
32
use \Exception;
33
34
/**
35
 * This is the main implementation class of the module
36
 *
37
 * The class should only define one public method: writeInstaller.
38
 *
39
 * All other methods and properties should be private. This example sets zipInstaller method to protected, so that it can be seen in the documentation.
40
 *
41
 * @package Developer
42
 */
43
abstract class MobileconfigSuperclass extends \core\DeviceConfig {
44
45
    private $instName;
46
    private $profileName;
47
    private $massagedInst;
48
    private $massagedProfile;
49
    private $massagedCountry;
50
    private $massagedConsortium;
51
    private $lang;
52
    static private $iPhonePayloadPrefix = "org.1x-config";
53
    private $clientCertUUID;
54
55
    /**
56
     * construct with the standard set of EAP methods we support, and preload
57
     * specialities
58
     */
59
    public function __construct() {
60
        parent::__construct();
61
        // that's what all variants support. Sub-classes can change it.
62
        $this->setSupportedEapMethods([\core\common\EAP::EAPTYPE_PEAP_MSCHAP2, \core\common\EAP::EAPTYPE_TTLS_PAP, \core\common\EAP::EAPTYPE_TTLS_MSCHAP2, \core\common\EAP::EAPTYPE_SILVERBULLET]);
63
        $this->specialities['internal:verify_userinput_suffix'] = _("It is not possible to actively verify the user input for suffix match; but if there is no 'Terms of Use' configured, the installer will display a corresponding hint to the user instead.");
64
    }
65
66
    /**
67
     * massage a name so that it becomes acceptable inside the plist XML
68
     * 
69
     * @param string $input the literal name
70
     * @return string
71
     */
72
    private function massageName($input) {
73
        return htmlspecialchars(strtolower(iconv("UTF-8", "US-ASCII//TRANSLIT", preg_replace(['/ /', '/\//'], '_', $input))), ENT_XML1, 'UTF-8');
74
    }
75
76
    /**
77
     * the general part of a mobileconfig file in plist format
78
     * @return string
79
     */
80
    private function generalPayload() {
81
        $tagline = sprintf(_("Network configuration profile '%s' of '%s' - provided by %s"), htmlspecialchars($this->profileName, ENT_XML1, 'UTF-8'), htmlspecialchars($this->instName, ENT_XML1, 'UTF-8'), CONFIG_CONFASSISTANT['CONSORTIUM']['display_name']);
82
83
        $eapType = $this->selectedEap;
84
        // simpler message for silverbullet
85
        if ($eapType['INNER'] == \core\common\EAP::NE_SILVERBULLET) {
86
            $tagline = sprintf(_("%s configuration for IdP '%s' - provided by %s"), \core\ProfileSilverbullet::PRODUCTNAME, htmlspecialchars($this->instName, ENT_XML1, 'UTF-8'), CONFIG_CONFASSISTANT['CONSORTIUM']['display_name']);
87
        }
88
89
        return "
90
      <key>PayloadDescription</key>
91
         <string>$tagline</string>
92
      <key>PayloadDisplayName</key>
93
         <string>" . CONFIG_CONFASSISTANT['CONSORTIUM']['display_name'] . "</string>
94
      <key>PayloadIdentifier</key>
95
         <string>" . self::$iPhonePayloadPrefix . ".$this->massagedConsortium.$this->massagedCountry.$this->massagedInst.$this->massagedProfile.$this->lang</string>
96
      <key>PayloadOrganization</key>
97
         <string>" . htmlspecialchars(iconv("UTF-8", "UTF-8//IGNORE", $this->attributes['general:instname'][0]), ENT_XML1, 'UTF-8') . ( $this->attributes['internal:profile_count'][0] > 1 ? " (" . htmlspecialchars(iconv("UTF-8", "UTF-8//IGNORE", $this->attributes['profile:name'][0]), ENT_XML1, 'UTF-8') . ")" : "") . "</string>
98
      <key>PayloadType</key>
99
         <string>Configuration</string>
100
      <key>PayloadUUID</key>
101
         <string>" . \core\common\Entity::uuid('', self::$iPhonePayloadPrefix . $this->massagedConsortium . $this->massagedCountry . $this->massagedInst . $this->massagedProfile) . "</string>
102
      <key>PayloadVersion</key>
103
         <integer>1</integer>";
104
    }
105
106
    const FILE_START = "<?xml version=\"1.0\" encoding=\"utf-8\"?>
107
<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\"
108
\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
109
<plist version=\"1.0\">
110
<dict>";
111
    const FILE_END = "</dict></plist>";
112
    const BUFFER_CONSENT_PRE = "
113
      <key>ConsentText</key>
114
         <dict>
115
            <key>default</key>
116
               <string>";
117
    const BUFFER_CONSENT_POST = "</string>
118
         </dict>
119
         ";
120
121
    /**
122
     * creates a ConsentText block if either Terms of Use are specified or the
123
     * user input hints should be displayed. Otherwise, produces nothing.
124
     * 
125
     * @return string
126
     */
127
    protected function consentBlock() {
128
        if (isset($this->attributes['support:info_file'])) {
129
            return MobileconfigSuperclass::BUFFER_CONSENT_PRE . htmlspecialchars(iconv("UTF-8", "UTF-8//TRANSLIT", $this->attributes['support:info_file'][0]), ENT_XML1, 'UTF-8') . MobileconfigSuperclass::BUFFER_CONSENT_POST;
130
        }
131
        if ($this->attributes['internal:verify_userinput_suffix'][0] != 0) {
132
            if (strlen($this->attributes['internal:realm'][0]) > 0) {
133
                return MobileconfigSuperclass::BUFFER_CONSENT_PRE . sprintf(_("Important Notice: your username must end with @%s!"), $this->attributes['internal:realm'][0]) . MobileconfigSuperclass::BUFFER_CONSENT_POST;
134
            }
135
            return MobileconfigSuperclass::BUFFER_CONSENT_PRE . _("Important Notice: your username MUST be in the form of xxx@yyy where the yyy is a common suffix identifying your Identity Provider. Please find out what to use there and enter the username in the correct format.") . MobileconfigSuperclass::BUFFER_CONSENT_POST;
136
        }
137
        return "";
138
    }
139
140
    /**
141
     * create the actual installer XML file
142
     * 
143
     * @return string filename of the generated installer
144
     *
145
     */
146
    public function writeInstaller() {
147
        $dom = textdomain(NULL);
148
        textdomain("devices");
149
150
        $this->loggerInstance->debug(4, "mobileconfig Module Installer start\n");
151
152
        // remove spaces and slashes (filename!), make sure it's simple ASCII only, then lowercase it
153
        // also escape htmlspecialchars
154
        // not all names and profiles have a name, so be prepared
155
156
        $this->loggerInstance->debug(5, "List of available attributes: " . var_export($this->attributes, TRUE));
157
158
        $this->instName = $this->attributes['general:instname'][0] ?? _("Unnamed Organisation");
159
        $this->profileName = $this->attributes['profile:name'][0] ?? _("Unnamed Profile");
160
161
        $this->massagedInst = $this->massageName($this->instName);
162
        $this->massagedProfile = $this->massageName($this->profileName);
163
        $this->massagedCountry = $this->massageName($this->attributes['internal:country'][0]);
164
        $this->massagedConsortium = $this->massageName(CONFIG_CONFASSISTANT['CONSORTIUM']['name']);
165
        $this->lang = preg_replace('/\..+/', '', setlocale(LC_ALL, "0"));
166
167
        $eapType = $this->selectedEap;
168
169
        $outputXml = self::FILE_START;
170
        $outputXml .= "<key>PayloadContent</key>
171
         <array>";
172
173
        // if we are in silverbullet, we will need a whole own block for the client credential
174
        // and also for the profile expiry
175
176
        $this->clientCertUUID = NULL;
177
        if ($eapType['INNER'] == \core\common\EAP::NE_SILVERBULLET) {
178
            $blockinfo = $this->clientP12Block();
179
            $outputXml .= $blockinfo['block'];
180
            $this->clientCertUUID = $blockinfo['UUID'];
181
        }
182
183
        $outputXml .= $this->allCA();
184
185
        $outputXml .= $this->allNetworkBlocks();
186
187
        $outputXml .= "</array>";
188
        $outputXml .= $this->generalPayload();
189
        $outputXml .= $this->consentBlock();
190
191
        if ($eapType['INNER'] == \core\common\EAP::NE_SILVERBULLET) {
192
            $outputXml .= $this->expiryBlock();
193
        }
194
        $outputXml .= self::FILE_END;
195
196
        file_put_contents('installer_profile', $outputXml);
197
198
        textdomain($dom);
199
200
        $fileName = $this->installerBasename . '.mobileconfig';
201
202
        if (!$this->sign) {
203
            rename("installer_profile", $fileName);
204
            return $fileName;
205
        }
206
        // still here? Then we are signing.
207
        $signing = system($this->sign . " installer_profile '$fileName' > /dev/null");
208
        if ($signing === FALSE) {
209
            $this->loggerInstance->debug(2, "Signing the mobileconfig installer $fileName FAILED!\n");
210
        }
211
        return $fileName;
212
    }
213
214
    /**
215
     * produces the HTML text to be displayed when clicking on the "help" button
216
     * besides the download button.
217
     * 
218
     * @return string
219
     */
220
    public function writeDeviceInfo() {
221
        $ssidCount = count($this->attributes['internal:SSID']);
222
        $certCount = count($this->attributes['internal:CAs'][0]);
223
        $out = "<p>" . _("For best results, please use the built-in browser (Safari) to open the configuration file.") . "</p>";
224
        $out .= "<p>";
225
        $out .= _("The profile will install itself after you click (or tap) the button. You will be asked for confirmation/input at several points:");
226
        $out .= "<ul>";
227
        $out .= "<li>" . _("to install the profile") . "</li>";
228
        $out .= "<li>" . ngettext("to accept the server certificate authority", "to accept the server certificate authorities", $certCount);
229
        if ($certCount > 1) {
230
            $out .= " " . sprintf(_("(%d times)"), $certCount);
231
        }
232
        $out .= "</li>";
233
        $out .= "<li>" . _("to enter the username and password you have been given by your organisation");
234
        if ($ssidCount > 1) {
235
            $out .= " " . sprintf(_("(%d times each, because %s is installed for %d SSIDs)"), $ssidCount, CONFIG_CONFASSISTANT['CONSORTIUM']['display_name'], $ssidCount);
236
        }
237
        $out .= "</li>";
238
        $out .= "</ul>";
239
        $out .= "</p>";
240
        return $out;
241
    }
242
243
    /**
244
     * collates a list of the UUIDs of all the CAs which are to be included in
245
     * the mobileconfig file
246
     * 
247
     * @return array
248
     */
249
    private function listCAUuids() {
250
        $retval = [];
251
        foreach ($this->attributes['internal:CAs'][0] as $ca) {
252
            $retval[] = $ca['uuid'];
253
        }
254
        return $retval;
255
    }
256
257
    /**
258
     * This is the XML structure subtree of a Network block which contains the
259
     * settings specific to Passpoint
260
     * 
261
     * @param array $consortiumOi list of consortiumOi to put into structure
262
     * @return string
263
     */
264
    private function passPointBlock($consortiumOi) {
265
        $retval = "
266
               <key>IsHotspot</key>
267
               <true/>
268
               <key>ServiceProviderRoamingEnabled</key>
269
               <true/>
270
               <key>DisplayedOperatorName</key>
271
               <string>" . CONFIG_CONFASSISTANT['CONSORTIUM']['display_name'] . " via Passpoint</string>";
272
        // if we don't know the realm, omit the entire DomainName key
273
        if (isset($this->attributes['internal:realm'])) {
274
            $retval .= "<key>DomainName</key>
275
               <string>";
276
            $retval .= $this->attributes['internal:realm'][0];
277
            $retval .= "</string>
278
                ";
279
        }
280
        $retval .= "                <key>RoamingConsortiumOIs</key>
281
                <array>";
282
        foreach ($consortiumOi as $oiValue) {
283
            $retval .= "<string>$oiValue</string>";
284
        }
285
        $retval .= "</array>";
286
        // this is an undocmented value found on the net. Does it do something useful?
287
        $retval .= "<key>_UsingHotspot20</key>
288
                <true/>
289
                ";
290
        // do we need to set NAIRealmName ? In Rel 1, probably yes, in Rel 2, 
291
        // no because ConsortiumOI is enough.
292
        // but which release is OS X doing? And what should we fill in, given
293
        // that we have thousands of realms? Try just eduroam.org
294
        if (CONFIG_CONFASSISTANT['CONSORTIUM']['name'] == "eduroam") {
295
            $retval .= "<key>NAIRealmNames</key>
296
                <array>
297
                    <string>eduroam.org</string>
298
                </array>";
299
        }
300
        return $retval;
301
    }
302
303
    private $serial;
304
    private $removeSerial;
305
    private $caSerial;
306
307
    /**
308
     * produces the EAP sub-block of a Network block
309
     * 
310
     * @param array $eapType EAP type in array notation
311
     * @return string
312
     */
313
    private function eapBlock($eapType) {
314
        $realm = $this->determineOuterIdString();
315
        $retval = "<key>EAPClientConfiguration</key>
316
                  <dict>
317
                      <key>AcceptEAPTypes</key>
318
                         <array>
319
                            <integer>" . $eapType['OUTER'] . "</integer>
320
                         </array>
321
                      <key>EAPFASTProvisionPAC</key>
322
                            <true />
323
                      <key>EAPFASTUsePAC</key>
324
                            <true />
325
                      <key>EAPFastProvisionPACAnonymously</key>
326
                            <false />
327
                      <key>OneTimeUserPassword</key>
328
                            <false />
329
";
330
        if ($realm !== NULL) {
331
            $retval .= "<key>OuterIdentity</key>
332
                                    <string>" . htmlspecialchars($realm, ENT_XML1, 'UTF-8') . "</string>
333
";
334
        }
335
        $retval .= "<key>PayloadCertificateAnchorUUID</key>
336
                         <array>";
337
        foreach ($this->listCAUuids() as $uuid) {
338
            if (in_array($uuid, $this->CAsAccountedFor)) {
339
                $retval .= "
340
<string>$uuid</string>";
341
            }
342
        }
343
        $retval .= "
344
                         </array>
345
                      <key>TLSAllowTrustExceptions</key>
346
                         <false />
347
                      <key>TLSTrustedServerNames</key>
348
                         <array>";
349
        foreach ($this->attributes['eap:server_name'] as $commonName) {
350
            $retval .= "
351
<string>$commonName</string>";
352
        }
353
        $retval .= "
354
                         </array>";
355
        if ($eapType['INNER'] == \core\common\EAP::NE_SILVERBULLET) {
356
            $retval .= "<key>UserName</key><string>" . $this->clientCert["certObject"]->username . "</string>";
357
        }
358
        $retval .= "
359
                      <key>TTLSInnerAuthentication</key>
360
                         <string>" . ($eapType['INNER'] == \core\common\EAP::NONE ? "PAP" : "MSCHAPv2") . "</string>
361
                   </dict>";
362
        return $retval;
363
    }
364
365
    /**
366
     * produces the Proxy sub-block of a Network block
367
     * 
368
     * @return string
369
     */
370
    protected function proxySettings() {
371
        $buffer = "<key>ProxyType</key>";
372
        if (isset($this->attributes['media:force_proxy'])) {
373
            // find the port delimiter. In case of IPv6, there are multiple ':' 
374
            // characters, so we have to find the LAST one
375
            $serverAndPort = explode(':', strrev($this->attributes['media:force_proxy'][0]), 2);
376
            // characters are still reversed, invert on use!
377
            $buffer .= "<string>Manual</string>
378
                  <key>ProxyServer</key>
379
                  <string>" . strrev($serverAndPort[1]) . "</string>
380
                  <key>ProxyServerPort</key>
381
                  <integer>" . strrev($serverAndPort[0]) . "</integer>
382
                  <key>ProxyPACFallbackAllowed</key>
383
                  <false/>";
384
        } else {
385
            $buffer .= "<string>Auto</string>
386
                  <key>ProxyPACFallbackAllowed</key>
387
                  <true/>";
388
        }
389
        return $buffer;
390
    }
391
392
    /**
393
     * produces an entire Network block
394
     * 
395
     * @param int                  $blocktype      which type of network block is this?
396
     * @param string|array|boolean $toBeConfigured variable part of the config. Single SSID or list of ConsortiumOi
397
     * @return string
398
     * @throws Exception
399
     */
400
    private function networkBlock($blocktype, $toBeConfigured) {
401
        $eapType = $this->selectedEap;
402
        switch ($blocktype) {
403
            case MobileconfigSuperclass::NETWORK_BLOCK_TYPE_SSID:
404
                if (!is_string($toBeConfigured)) {
405
                    throw new Exception("SSID must be a string!");
406
                }
407
                $escapedSSID = htmlspecialchars($toBeConfigured, ENT_XML1, 'UTF-8');
408
                $payloadIdentifier = "wifi." . $this->serial;
409
                $payloadShortName = sprintf(_("SSID %s"), $escapedSSID);
410
                $payloadName = sprintf(_("%s configuration for network name %s"), CONFIG_CONFASSISTANT['CONSORTIUM']['display_name'], $escapedSSID);
411
                $encryptionTypeString = "WPA";
412
                $setupModesString = "";
413
                $wifiNetworkIdentification = "<key>SSID_STR</key>
414
                  <string>$escapedSSID</string>";
415
                break;
416
            case MobileconfigSuperclass::NETWORK_BLOCK_TYPE_WIRED:
417
                if (!is_bool($toBeConfigured)) {
418
                    throw new Exception("We expected a TRUE here!");
419
                }
420
                $payloadIdentifier = "firstactiveethernet";
421
                $payloadShortName = _("Wired Network");
422
                $payloadName = sprintf(_("%s configuration for wired network"), CONFIG_CONFASSISTANT['CONSORTIUM']['display_name']);
423
                $encryptionTypeString = "any";
424
                $setupModesString = "
425
               <key>SetupModes</key>
426
                  <array>
427
                     <string>System</string>
428
                  </array>";
429
                $wifiNetworkIdentification = "";
430
                break;
431
            case MobileconfigSuperclass::NETWORK_BLOCK_TYPE_CONSORTIUMOIS:
432
                if (!is_array($toBeConfigured)) {
433
                    throw new Exception("ConsortiumOI list must be an array!");
434
                }
435
                $payloadIdentifier = "hs20";
436
                $payloadShortName = _("Hotspot 2.0 Settings");
437
                $payloadName = sprintf(_("%s Hotspot 2.0 configuration"), CONFIG_CONFASSISTANT['CONSORTIUM']['display_name']);
438
                $encryptionTypeString = "WPA";
439
                $setupModesString = "";
440
                $wifiNetworkIdentification = $this->passPointBlock($toBeConfigured);
441
                break;
442
            default:
443
                throw new Exception("This type of network block is unknown!");
444
        }
445
        $retval = "<dict>";
446
        $retval .= $this->eapBlock($eapType);
447
        $retval .= "<key>EncryptionType</key>
448
                  <string>$encryptionTypeString</string>
449
               <key>HIDDEN_NETWORK</key>
450
                  <true />
451
               <key>PayloadDescription</key>
452
                  <string>$payloadName</string>
453
               <key>PayloadDisplayName</key>
454
                  <string>$payloadShortName</string>
455
               <key>PayloadIdentifier</key>
456
                  <string>" . self::$iPhonePayloadPrefix . ".$this->massagedConsortium.$this->massagedCountry.$this->massagedInst.$this->massagedProfile.$this->lang.$payloadIdentifier</string>
457
               <key>PayloadOrganization</key>
458
                  <string>" . $this->massagedConsortium . ".1x-config.org</string>
459
               <key>PayloadType</key>
460
                  <string>com.apple." . ($blocktype == MobileconfigSuperclass::NETWORK_BLOCK_TYPE_WIRED ? "firstactiveethernet" : "wifi") . ".managed</string>";
461
        $retval .= $this->proxySettings();
462
        $retval .= $setupModesString;
463
        if ($eapType['INNER'] == \core\common\EAP::NE_SILVERBULLET) {
464
            if ($this->clientCertUUID === NULL) {
465
                throw new Exception("Silverbullet REQUIRES a client certificate and we need to know the UUID!");
466
            }
467
            $retval .= "<key>PayloadCertificateUUID</key>
468
                        <string>$this->clientCertUUID</string>";
469
        }
470
        $retval .= "
471
               <key>PayloadUUID</key>
472
                  <string>" . \core\common\Entity::uuid() . "</string>
473
               <key>PayloadVersion</key>
474
                  <integer>1</integer>
475
                  $wifiNetworkIdentification</dict>";
476
        $this->serial = $this->serial + 1;
477
        return $retval;
478
    }
479
480
    /**
481
     * Produces a Network block which sets a network to manual join (we don't
482
     * get any closer to removing a network in mobileconfig)
483
     * 
484
     * @param string $ssid the SSID to set to manual join only
485
     * @return string
486
     */
487
    private function removenetworkBlock($ssid) {
488
        $retval = "
489
<dict>
490
	<key>AutoJoin</key>
491
	<false/>
492
	<key>EncryptionType</key>
493
	<string>None</string>
494
	<key>HIDDEN_NETWORK</key>
495
	<false/>
496
	<key>IsHotspot</key>
497
	<false/>
498
	<key>PayloadDescription</key>
499
	<string>" . sprintf(_("This SSID should not be used after bootstrapping %s"), CONFIG_CONFASSISTANT['CONSORTIUM']['display_name']) . "</string>
500
	<key>PayloadDisplayName</key>
501
	<string>" . _("Disabled WiFi network") . "</string>
502
	<key>PayloadIdentifier</key>
503
	<string>" . self::$iPhonePayloadPrefix . ".$this->massagedConsortium.$this->massagedCountry.$this->massagedInst.$this->massagedProfile.$this->lang.wifi.disabled.$this->removeSerial</string>
504
	<key>PayloadType</key>
505
	<string>com.apple.wifi.managed</string>
506
	<key>PayloadUUID</key>
507
	<string>" . \core\common\Entity::uuid() . "</string>
508
	<key>PayloadVersion</key>
509
	<real>1</real>";
510
        $retval .= $this->proxySettings();
511
        $retval .= "<key>SSID_STR</key>
512
	<string>$ssid</string>
513
</dict>
514
";
515
        return $retval;
516
    }
517
518
    const NETWORK_BLOCK_TYPE_SSID = 100;
519
    const NETWORK_BLOCK_TYPE_CONSORTIUMOIS = 101;
520
    const NETWORK_BLOCK_TYPE_WIRED = 102;
521
522
    /**
523
     * produces the entire series of Network blocks; all for SSID-based, 
524
     * Passpoint-based, wired, and manual-select only SSIDs
525
     * 
526
     * @return string
527
     */
528
    private function allNetworkBlocks() {
529
        $retval = "";
530
        $this->serial = 0;
531
532
        foreach (array_keys($this->attributes['internal:SSID']) as $ssid) {
533
            $retval .= $this->networkBlock(MobileconfigSuperclass::NETWORK_BLOCK_TYPE_SSID, $ssid);
534
        }
535
        if (isset($this->attributes['media:wired']) && get_class($this) == "devices\apple_mobileconfig\Device_mobileconfig_os_x") {
536
            $retval .= $this->networkBlock(MobileconfigSuperclass::NETWORK_BLOCK_TYPE_WIRED, TRUE);
537
        }
538
        if (count($this->attributes['internal:consortia']) > 0) {
539
            $retval .= $this->networkBlock(MobileconfigSuperclass::NETWORK_BLOCK_TYPE_CONSORTIUMOIS, $this->attributes['internal:consortia']);
540
        }
541
        if (isset($this->attributes['media:remove_SSID'])) {
542
            $this->removeSerial = 0;
543
            foreach ($this->attributes['media:remove_SSID'] as $removeSSID) {
544
                $retval .= $this->removenetworkBlock($removeSSID);
545
                $this->removeSerial = $this->removeSerial + 1;
546
            }
547
        }
548
        return $retval;
549
    }
550
551
    /**
552
     * collates a block with all CAs that are to be included in the mobileconfig
553
     * 
554
     * @return string
555
     */
556
    private function allCA() {
557
        $retval = "";
558
        $this->caSerial = 0;
559
        foreach ($this->attributes['internal:CAs'][0] as $ca) {
560
            $retval .= $this->caBlob($ca);
561
            $this->caSerial = $this->caSerial + 1;
562
        }
563
        return $retval;
564
    }
565
566
    /**
567
     * creates a Cert block containing a client certificate (used in SB only)
568
     * @return array the block itself, and the UUID of the certificate
569
     * @throws Exception
570
     */
571
    private function clientP12Block() {
572
        if (!is_array($this->clientCert)) {
573
            throw new Exception("the client block was called but there is no client certificate!");
574
        }
575
        $binaryBlob = $this->clientCert["certdata_nointermediate"];
576
        $mimeBlob = base64_encode($binaryBlob);
577
        $mimeFormatted = chunk_split($mimeBlob, 52, "\r\n");
578
        $payloadUUID = \core\common\Entity::uuid('', $mimeBlob);
579
        return ["block" => "<dict>" .
580
            // we don't include the import password. It's displayed on screen, and should be input by the user.
581
            // <key>Password</key>
582
            //   <string>" . $this->clientCert['password'] . "</string>
583
            "<key>PayloadCertificateFileName</key>
584
                     <string>cert-cli.pfx</string>
585
                  <key>PayloadContent</key>
586
                     <data>
587
$mimeFormatted
588
                     </data>
589
                  <key>PayloadDescription</key>
590
                     <string>MIME Base-64 encoded PKCS#12 Client Certificate</string>
591
                  <key>PayloadDisplayName</key>
592
                     <string>" . _("eduroam user certificate") . "</string>
593
                  <key>PayloadIdentifier</key>
594
                     <string>com.apple.security.pkcs12.$payloadUUID</string>
595
                  <key>PayloadType</key>
596
                     <string>com.apple.security.pkcs12</string>
597
                  <key>PayloadUUID</key>
598
                     <string>$payloadUUID</string>
599
                  <key>PayloadVersion</key>
600
                     <integer>1</integer>
601
                </dict>",
602
            "UUID" => $payloadUUID,];
603
    }
604
605
    /**
606
     * creates an Expiry block. This is only done in SB; the profile expires
607
     * when the client cert expires.
608
     * 
609
     * @return string
610
     * @throws Exception
611
     */
612
    private function expiryBlock() {
613
        if (!is_array($this->clientCert)) {
614
            throw new Exception("the expiry block was called but there is no client certificate!");
615
        }
616
        $expiryTime = new \DateTime($this->clientCert['certObject']->expiry);
617
        return "<key>RemovalDate</key>
618
        <date>" . $expiryTime->format("Y-m-d") . "T" . $expiryTime->format("H:i:s") . "Z</date>";
619
    }
620
621
    private $CAsAccountedFor = [];
622
623
    /**
624
     * creates a block for one single CA
625
     * 
626
     * @param array $ca the CA for which to generate the XML block
627
     * @return string
628
     */
629
    private function caBlob($ca) {
630
        $stream = "";
631
        if (!in_array($ca['uuid'], $this->CAsAccountedFor)) { // skip if this is a duplicate
632
            // cut lines with CERTIFICATE
633
            $stage1 = preg_replace('/-----BEGIN CERTIFICATE-----/', '', $ca['pem']);
634
            $stage2 = preg_replace('/-----END CERTIFICATE-----/', '', $stage1);
635
            $trimmedPem = trim($stage2);
636
637
            $stream = "
638
            <dict>
639
               <key>PayloadCertificateFileName</key>
640
               <string>" . $ca['uuid'] . ".der</string>
641
               <key>PayloadContent</key>
642
               <data>
643
" . $trimmedPem . "</data>
644
               <key>PayloadDescription</key>
645
               <string>" . _("Your Identity Providers Certification Authority") . "</string>
646
               <key>PayloadDisplayName</key>
647
               <string>" . _("Identity Provider's CA") . "</string>
648
               <key>PayloadIdentifier</key>
649
               <string>" . self::$iPhonePayloadPrefix . ".$this->massagedConsortium.$this->massagedCountry.$this->massagedInst.$this->massagedProfile.credential.$this->caSerial</string>
650
               <key>PayloadOrganization</key>
651
               <string>" . $this->massagedConsortium . ".1x-config.org</string>
652
               <key>PayloadType</key>
653
               <string>com.apple.security.root</string>
654
               <key>PayloadUUID</key><string>" . $ca['uuid'] . "</string>
655
               <key>PayloadVersion</key>
656
               <integer>1</integer>
657
            </dict>";
658
            $this->CAsAccountedFor[] = $ca['uuid'];
659
        }
660
        return $stream;
661
    }
662
663
}
664