Passed
Push — release_2_0 ( eda638...54acd9 )
by Stefan
07:59
created

MobileconfigSuperclass::allCA()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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