Issues (173)

Security Analysis    13 potential vulnerabilities

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  Response Splitting (3)
Response Splitting can be used to send arbitrary responses.
  File Manipulation (6)
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Cross-Site Scripting (1)
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

apple_mobileconfig/MobileconfigSuperclass.php (1 issue)

Labels
Severity
1
<?php
2
3
/*
4
 * *****************************************************************************
5
 * Contributions to this work were made on behalf of the GÉANT project, a 
6
 * project that has received funding from the European Union’s Framework 
7
 * Programme 7 under Grant Agreements No. 238875 (GN3) and No. 605243 (GN3plus),
8
 * Horizon 2020 research and innovation programme under Grant Agreements No. 
9
 * 691567 (GN4-1) and No. 731122 (GN4-2).
10
 * On behalf of the aforementioned projects, GEANT Association is the sole owner
11
 * of the copyright in all material which was developed by a member of the GÉANT
12
 * project. GÉANT Vereniging (Association) is registered with the Chamber of 
13
 * Commerce in Amsterdam with registration number 40535155 and operates in the 
14
 * UK as a branch of GÉANT Vereniging.
15
 * 
16
 * Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands. 
17
 * UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK
18
 *
19
 * License: see the web/copyright.inc.php file in the file structure or
20
 *          <base_url>/copyright.php after deploying the software
21
 */
22
23
/**
24
 * This file contains the installer for iOS devices and Apple 10.7 Lion
25
 *
26
 *
27
 * @author Stefan Winter <[email protected]>
28
 * @package Developer
29
 */
30
31
namespace devices\apple_mobileconfig;
32
33
use \Exception;
0 ignored issues
show
The type \Exception was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
34
35
/**
36
 * This is the main implementation class of the module
37
 *
38
 * The class should only define one public method: writeInstaller.
39
 *
40
 * All other methods and properties should be private. This example sets zipInstaller method to protected, so that it can be seen in the documentation.
41
 *
42
 * @package Developer
43
 */
44
abstract class MobileconfigSuperclass extends \core\DeviceConfig
45
{
46
47
    /**
48
     * institution name in preferred language
49
     * 
50
     * @var string
51
     */
52
    private $instName;
53
    
54
    /**
55
     * profile name in preferred language
56
     * 
57
     * @var string
58
     */
59
    private $profileName;
60
    
61
    /**
62
     * institution name, massaged to make it fit into the XML payload descriptor
63
     * 
64
     * @var string
65
     */
66
    private $massagedInst;
67
    
68
    /**
69
     * profile name, massaged to make it fit into the XML payload descriptor
70
     * 
71
     * @var string
72
     */
73
    private $massagedProfile;
74
    
75
    /**
76
     * country name, massaged to make it fit into the XML payload descriptor
77
     * 
78
     * @var string
79
     */
80
    private $massagedCountry;
81
    
82
    /**
83
     * consortium name, massaged to make it fit into the XML payload descriptor
84
     * 
85
     * @var string
86
     */
87
    private $massagedConsortium;
88
    
89
    /**
90
     * current language, without any .CHARSET suffixes
91
     * 
92
     * @var string
93
     */
94
    private $lang;
95
    
96
    /**
97
     * if we include a client certificate (silverbullet), the UUID of the certificate
98
     * 
99
     * @var string
100
     */
101
    private $clientCertUUID;
102
103
    /**
104
     * iterator to uniquely identify every Wi-Fi payload with a suffix in the file
105
     * 
106
     * @var integer
107
     */
108
    private $serial;
109
    
110
    /**
111
     * iterator to uniquely identify every removal payload with a suffix in the file
112
     * @var integer
113
     */
114
    private $removeSerial;
115
    
116
    /**
117
     * iterator to uniquely identify evera CA payload with a suffix in the file
118
     * 
119
     * @var integer
120
     */
121
    private $caSerial;
122
    
123
    /**
124
     * we need to be cautious not to include a CA twice, even if the admin
125
     * included it twice in the CAT config. This variable takes note of all
126
     * CA certs we already added to the file.
127
     * 
128
     * @var array
129
     */
130
    private $CAsAccountedFor = [];
131
    
132
    /**
133
     * we need a globally valid domain anchor, independent of the consortium
134
     */
135
    const IPHONE_PAYLOAD_PREFIX = "org.1x-config";
136
    
137
138
    /**
139
     * construct with the standard set of EAP methods we support, and preload
140
     * specialities
141
     */
142
    public function __construct()
143
    {
144
        parent::__construct();
145
        \core\common\Entity::intoThePotatoes();
146
        // that's what all variants support. Sub-classes can change it.
147
        $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]);
148
        foreach(\core\common\EAP::listKnownEAPTypes() as $eapType) {
149
            if ($eapType->isPasswordRequired() || $eapType->isPasswordOptional()) {
150
                $this->specialities['internal:verify_userinput_suffix'][serialize($eapType->getArrayRep())] = _("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.");
151
                $this->specialities['media:consortium_OI'][serialize($eapType->getArrayRep())] = _("Passpoint networks are not provisioned due to severe UI limitations during install time.");
152
            }
153
        }
154
        \core\common\Entity::outOfThePotatoes();
155
    }
156
157
    /**
158
     * massage a name so that it becomes acceptable inside the plist XML
159
     * 
160
     * @param string $input the literal name
161
     * @return string
162
     */
163
    private function massageName($input)
164
    {
165
        return htmlspecialchars(strtolower(iconv("UTF-8", "US-ASCII//TRANSLIT", preg_replace(['/ /', '/\//'], '_', $input))), ENT_XML1, 'UTF-8');
166
    }
167
168
    /**
169
     * the general part of a mobileconfig file in plist format
170
     * @return string
171
     */
172
    private function generalPayload()
173
    {
174
        \core\common\Entity::intoThePotatoes();
175
        $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']);
176
177
        $eapType = $this->selectedEap;
178
        // simpler message for silverbullet
179
        if ($eapType['INNER'] == \core\common\EAP::NE_SILVERBULLET) {
180
            $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']);
181
        }
182
183
        $retval = "
184
      <key>PayloadDescription</key>
185
         <string>$tagline</string>
186
      <key>PayloadDisplayName</key>
187
         <string>" . \config\ConfAssistant::CONSORTIUM['display_name'] . "</string>
188
      <key>PayloadIdentifier</key>
189
         <string>" . self::IPHONE_PAYLOAD_PREFIX . ".$this->massagedConsortium.$this->massagedCountry.$this->massagedInst.$this->massagedProfile.$this->lang</string>
190
      <key>PayloadOrganization</key>
191
         <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>
192
      <key>PayloadType</key>
193
         <string>Configuration</string>
194
      <key>PayloadUUID</key>
195
         <string>" . \core\common\Entity::uuid('', self::IPHONE_PAYLOAD_PREFIX . $this->massagedConsortium . $this->massagedCountry . $this->massagedInst . $this->massagedProfile) . "</string>
196
      <key>PayloadVersion</key>
197
         <integer>1</integer>";
198
        \core\common\Entity::outOfThePotatoes();
199
        return $retval;
200
    }
201
202
    const FILE_START = "<?xml version=\"1.0\" encoding=\"utf-8\"?>
203
<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\"
204
\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
205
<plist version=\"1.0\">
206
<dict>";
207
    const FILE_END = "</dict></plist>";
208
    const BUFFER_CONSENT_PRE = "
209
      <key>ConsentText</key>
210
         <dict>
211
            <key>default</key>
212
               <string>";
213
    const BUFFER_CONSENT_POST = "</string>
214
         </dict>
215
         ";
216
217
    /**
218
     * creates a ConsentText block if either Terms of Use are specified or the
219
     * user input hints should be displayed. Otherwise, produces nothing.
220
     * 
221
     * @return string
222
     */
223
    protected function consentBlock()
224
    {
225
        \core\common\Entity::intoThePotatoes();
226
        if (isset($this->attributes['support:info_file'])) {
227
            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;
228
        }
229
        if ($this->attributes['internal:verify_userinput_suffix'][0] != 0) {
230
            if ($this->attributes['internal:hint_userinput_suffix'][0] != 0) {
231
                $retval = MobileconfigSuperclass::BUFFER_CONSENT_PRE . sprintf(_("Important Notice: your username MUST end exactly with '...@%s' !"), $this->attributes['internal:realm'][0]) . MobileconfigSuperclass::BUFFER_CONSENT_POST;
232
                \core\common\Entity::outOfThePotatoes();
233
                return $retval;
234
            } else { 
235
            if (strlen($this->attributes['internal:realm'][0]) > 0) {
236
                /// note space between variable and exclamation mark - makes sure users don't mistakenly think the exclamation mark is part of the required username!
237
                $retval = MobileconfigSuperclass::BUFFER_CONSENT_PRE . sprintf(_("Important Notice: your username MUST contain an '@' and end with ...%s !"), $this->attributes['internal:realm'][0]) . MobileconfigSuperclass::BUFFER_CONSENT_POST;
238
                \core\common\Entity::outOfThePotatoes();
239
                return $retval;
240
            }
241
            $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;
242
            \core\common\Entity::outOfThePotatoes();
243
            return $retval;
244
            }
245
        }
246
        \core\common\Entity::outOfThePotatoes();
247
        return "";
248
    }
249
250
    /**
251
     * create the actual installer XML file
252
     * 
253
     * @return string filename of the generated installer
254
     */
255
    public function writeInstaller()
256
    {
257
        \core\common\Entity::intoThePotatoes();
258
259
        $this->loggerInstance->debug(4, "mobileconfig Module Installer start\n");
260
261
        // remove spaces and slashes (filename!), make sure it's simple ASCII only, then lowercase it
262
        // also escape htmlspecialchars
263
        // not all names and profiles have a name, so be prepared
264
265
        $this->loggerInstance->debug(5, "List of available attributes: " . var_export($this->attributes, TRUE));
266
267
        $this->instName = $this->attributes['general:instname'][0] ?? _("Unnamed Organisation");
268
        $this->profileName = $this->attributes['profile:name'][0] ?? _("Unnamed Profile");
269
270
        $this->massagedInst = $this->massageName($this->instName);
271
        $this->massagedProfile = $this->massageName($this->profileName);
272
        $this->massagedCountry = $this->massageName($this->attributes['internal:country'][0]);
273
        $this->massagedConsortium = $this->massageName(\config\ConfAssistant::CONSORTIUM['name']);
274
        $this->lang = preg_replace('/\..+/', '', setlocale(LC_ALL, "0"));
275
276
        $eapType = $this->selectedEap;
277
278
        $outputXml = self::FILE_START;
279
        $outputXml .= "<key>PayloadContent</key>
280
         <array>";
281
282
        // if we are in silverbullet, we will need a whole own block for the client credential
283
        // and also for the profile expiry
284
285
        $this->clientCertUUID = NULL;
286
        if ($eapType['INNER'] == \core\common\EAP::NE_SILVERBULLET) {
287
            $blockinfo = $this->clientP12Block();
288
            $outputXml .= $blockinfo['block'];
289
            $this->clientCertUUID = $blockinfo['UUID'];
290
        }
291
292
        $outputXml .= $this->allCA();
293
294
        $outputXml .= $this->allNetworkBlocks();
295
296
        $outputXml .= "</array>";
297
        $outputXml .= $this->generalPayload();
298
        $outputXml .= $this->consentBlock();
299
300
        if ($eapType['INNER'] == \core\common\EAP::NE_SILVERBULLET) {
301
            $outputXml .= $this->expiryBlock();
302
        }
303
        $outputXml .= self::FILE_END;
304
305
        file_put_contents('installer_profile', $outputXml);
306
307
        $fileName = $this->installerBasename . '.mobileconfig';
308
309
        if (!$this->sign) {
310
            rename("installer_profile", $fileName);
311
            \core\common\Entity::outOfThePotatoes();
312
            return $fileName;
313
        }
314
        // still here? Then we are signing.
315
        $retval = 0;
316
        $signing = system($this->sign . " installer_profile '$fileName' > /dev/null", $retval);
317
        if ($retval !== 0 || $signing === FALSE) {
318
            $this->loggerInstance->debug(2, "Signing the mobileconfig installer $fileName FAILED!\n");
319
            // we are passing a name that will be then used as a path - this will not exist, hence an error will
320
            // be generated
321
            return("no_go");
322
        }
323
        \core\common\Entity::outOfThePotatoes();
324
        return $fileName;
325
    }
326
327
    /**
328
     * produces the HTML text to be displayed when clicking on the "help" button
329
     * besides the download button.
330
     * 
331
     * @return string
332
     */
333
    public function writeDeviceInfo()
334
    {
335
        \core\common\Entity::intoThePotatoes();
336
        $ssidCount = 0;
337
        $oiCount = 0;
338
        foreach ($this->attributes['internal:networks'] as $netDetail) {
339
            $ssidCount = $ssidCount + count($netDetail['ssid']);
340
            $oiCount = $oiCount + count($netDetail['oi']);
341
        }
342
        $certCount = count($this->attributes['internal:CAs'][0]);
343
        $out = "<p>" . _("For best results, please use the built-in browser (Safari) to open the configuration file.") . "</p>";
344
        $out .= "<p>";
345
        $out .= _("The profile will install itself after you click (or tap) the button. You will be asked for confirmation/input at several points:");
346
        $out .= "<ul>";
347
        $out .= "<li>" . _("to install the profile") . "</li>";
348
        $out .= "<li>" . ngettext("to accept the server certificate authority", "to accept the server certificate authorities", $certCount);
349
        if ($certCount > 1) {
350
            $out .= " " . sprintf(_("(%d times)"), $certCount);
351
        }
352
        $out .= "</li>";
353
        $out .= "<li>" . _("to enter the username and password you have been given by your organisation");
354
        if ($ssidCount > 1) {
355
            $out .= " " . sprintf(_("(%d times each, because %d SSIDs and %d Passpoint networks are installed)"), $ssidCount+$oiCount, $ssidCount, $oiCount);
356
        }
357
        $out .= "</li>";
358
        $out .= "</ul>";
359
        $out .= "</p>";
360
        \core\common\Entity::outOfThePotatoes();
361
        return $out;
362
    }
363
364
    /**
365
     * collates a list of the UUIDs of all the CAs which are to be included in
366
     * the mobileconfig file
367
     * 
368
     * @return array
369
     */
370
    private function listCAUuids()
371
    {
372
        $retval = [];
373
        foreach ($this->attributes['internal:CAs'][0] as $ca) {
374
            $retval[] = $ca['uuid'];
375
        }
376
        return $retval;
377
    }
378
379
    /**
380
     * This is the XML structure subtree of a Network block which contains the
381
     * settings specific to Passpoint
382
     * 
383
     * @param array  $consortiumOi list of consortiumOi to put into structure
384
     * @param string $oiName       the pretty-print name of the RCOI
385
     * @return string
386
     */
387
    private function passPointBlock($consortiumOi, $oiName)
388
    {
389
        $retval = "
390
               <key>IsHotspot</key>
391
               <true/>
392
               <key>ServiceProviderRoamingEnabled</key>
393
               <true/>
394
               <key>DisplayedOperatorName</key>
395
               <string>" . $oiName . "</string>";
396
        // if we don't know the realm, omit the entire DomainName key
397
        if (isset($this->attributes['internal:realm'])) {
398
            $retval .= "<key>DomainName</key>
399
               <string>";
400
            $retval .= $this->attributes['internal:realm'][0];
401
            $retval .= "</string>
402
                ";
403
        }
404
        $retval .= "                <key>RoamingConsortiumOIs</key>
405
                <array>";
406
407
        foreach ($consortiumOi as $oneCons) {
408
            $retval .= "<string>" . strtoupper($oneCons) . "</string>";
409
        }
410
411
        $retval .= "</array>";
412
        // this is an undocumented value found on the net. Does it do something useful?
413
        $retval .= "<key>_UsingHotspot20</key>
414
                <true/>
415
                ";
416
        // do we need to set NAIRealmName ? In Rel 1, probably yes, in Rel 2, 
417
        // no because ConsortiumOI is enough.
418
        // but which release is OS X doing? And what should we fill in, given
419
        // that we have thousands of realms? Try just eduroam.org
420
        // 
421
        // tests from Hideaki suggest it's better not to set it; if Roaming
422
        // consortium OI and NAIRealmNames are both set, connecting to a hotspot
423
        // with just RCOI does not work
424
        /* if (\config\ConfAssistant::CONSORTIUM['name'] == "eduroam") {
425
            $retval .= "<key>NAIRealmNames</key>
426
                <array>
427
                    <string>eduroam.org</string>
428
                </array>";
429
        }*/
430
        return $retval;
431
    }
432
433
    /**
434
     * produces the EAP sub-block of a Network block
435
     * 
436
     * @param array $eapType EAP type in array notation
437
     * @return string
438
     */
439
    private function eapBlock($eapType)
440
    {
441
        $realm = $this->determineOuterIdString();
442
        $retval = "<key>EAPClientConfiguration</key>
443
                  <dict>
444
                      <key>AcceptEAPTypes</key>
445
                         <array>
446
                            <integer>" . $eapType['OUTER'] . "</integer>
447
                         </array>
448
                      <key>EAPFASTProvisionPAC</key>
449
                            <true />
450
                      <key>EAPFASTUsePAC</key>
451
                            <true />
452
                      <key>EAPFastProvisionPACAnonymously</key>
453
                            <false />
454
                      <key>OneTimeUserPassword</key>
455
                            <false />
456
";
457
        if ($realm !== NULL) {
458
            $retval .= "<key>OuterIdentity</key>
459
                                    <string>" . htmlspecialchars($realm, ENT_XML1, 'UTF-8') . "</string>
460
";
461
        }
462
        $retval .= "<key>PayloadCertificateAnchorUUID</key>
463
                         <array>";
464
        foreach ($this->listCAUuids() as $uuid) {
465
            if (in_array($uuid, $this->CAsAccountedFor)) {
466
                $retval .= "
467
<string>$uuid</string>";
468
            }
469
        }
470
        $retval .= "
471
                         </array>
472
                      <key>TLSAllowTrustExceptions</key>
473
                         <false />
474
                      <key>TLSTrustedServerNames</key>
475
                         <array>";
476
        foreach ($this->attributes['eap:server_name'] as $commonName) {
477
            $retval .= "
478
<string>$commonName</string>";
479
        }
480
        $retval .= "
481
                         </array>";
482
        if ($eapType['INNER'] == \core\common\EAP::NE_SILVERBULLET) {
483
            $retval .= "<key>UserName</key><string>" . $this->clientCert["certObject"]->username . "</string>";
484
        }
485
        $retval .= "
486
                      <key>TTLSInnerAuthentication</key>
487
                         <string>" . ($eapType['INNER'] == \core\common\EAP::NE_PAP ? "PAP" : "MSCHAPv2") . "</string>
488
                   </dict>";
489
        return $retval;
490
    }
491
492
    /**
493
     * produces the Proxy sub-block of a Network block
494
     * 
495
     * @return string
496
     */
497
    protected function proxySettings()
498
    {
499
        $buffer = "<key>ProxyType</key>";
500
        if (isset($this->attributes['media:force_proxy'])) {
501
            // find the port delimiter. In case of IPv6, there are multiple ':' 
502
            // characters, so we have to find the LAST one
503
            $serverAndPort = explode(':', strrev($this->attributes['media:force_proxy'][0]), 2);
504
            // characters are still reversed, invert on use!
505
            $buffer .= "<string>Manual</string>
506
                  <key>ProxyServer</key>
507
                  <string>" . strrev($serverAndPort[1]) . "</string>
508
                  <key>ProxyServerPort</key>
509
                  <integer>" . strrev($serverAndPort[0]) . "</integer>
510
                  <key>ProxyPACFallbackAllowed</key>
511
                  <false/>";
512
        } else {
513
            $buffer .= "<string>Auto</string>
514
                  <key>ProxyPACFallbackAllowed</key>
515
                  <true/>";
516
        }
517
        return $buffer;
518
    }
519
520
    /**
521
     * iOS 8 introduced the new value "WPA2" to lock out WPA ("1"). Make sure
522
     * we use it for the most recent gen of mobileconfig for iOS.
523
     * 
524
     * According to reports on the eduroam dev ML, also recent macOS supports it
525
     * so add it there, too (spec is silent on macOS versions).
526
     * 
527
     * @return string either "WPA" or "WPA2
528
     */
529
    private function encryptionString() {
530
        if (
531
                get_class($this) == "devices\apple_mobileconfig\DeviceMobileconfigIos12plus" || 
532
                get_class($this) == "devices\apple_mobileconfig\DeviceMobileconfigOsX"
533
           ) {
534
                    return "WPA2";
535
                } else {
536
                    return "WPA";
537
                }
538
    }
539
    
540
    /**
541
     * produces an entire Network block
542
     * 
543
     * @param int                  $blocktype      which type of network block is this?
544
     * @param string|array|boolean $toBeConfigured variable part of the config. Single SSID or list of ConsortiumOi
545
     * @param string               $prettyName     name with which to present the network to users
546
     * @return string
547
     * @throws Exception
548
     */
549
    private function networkBlock($blocktype, $toBeConfigured, $prettyName)
550
    {
551
        \core\common\Entity::intoThePotatoes();
552
        $eapType = $this->selectedEap;
553
        switch ($blocktype) {
554
            case MobileconfigSuperclass::NETWORK_BLOCK_TYPE_SSID:
555
                if (!is_string($toBeConfigured)) {
556
                    throw new Exception("SSID must be a string!");
557
                }
558
                $escapedSSID = htmlspecialchars($toBeConfigured, ENT_XML1, 'UTF-8');
559
                $payloadIdentifier = "wifi." . $this->serial;
560
                $payloadShortName = sprintf(_("%s - SSID %s"), $prettyName, $escapedSSID);
561
                $payloadName = sprintf(_("%s configuration for network name %s"), $prettyName, $escapedSSID);
562
                $encryptionTypeString = $this->encryptionString();
563
                $setupModesString = "";
564
                $wifiNetworkIdentification = "<key>SSID_STR</key>
565
                  <string>$escapedSSID</string>";
566
                break;
567
            case MobileconfigSuperclass::NETWORK_BLOCK_TYPE_WIRED:
568
                if (!is_bool($toBeConfigured)) {
569
                    throw new Exception("We expected a TRUE here!");
570
                }
571
                $payloadIdentifier = "firstactiveethernet";
572
                $payloadShortName = sprintf(_("%s - Wired Network"), $prettyName);
573
                $payloadName = sprintf(_("%s configuration for wired network"), $prettyName);
574
                $encryptionTypeString = "any";
575
                $setupModesString = "
576
               <key>SetupModes</key>
577
                  <array>
578
                     <string>System</string>
579
                  </array>";
580
                $wifiNetworkIdentification = "";
581
                break;
582
            case MobileconfigSuperclass::NETWORK_BLOCK_TYPE_CONSORTIUMOIS:
583
                if (!is_array($toBeConfigured)) {
584
                    throw new Exception("ConsortiumOI must be an array!");
585
                }
586
                if (count($toBeConfigured) == 0) {
587
                    return "";
588
                }
589
                $payloadIdentifier = "hs20.".implode('-',$toBeConfigured);
590
                $payloadShortName = sprintf(_("%s - RCOI"), $prettyName);
591
                $payloadName = sprintf(_("%s configuration (Passpoint RCOI)"),$prettyName);
592
                $encryptionTypeString = $this->encryptionString();
593
                $setupModesString = "";
594
                $wifiNetworkIdentification = $this->passPointBlock($toBeConfigured, $prettyName);
595
                break;
596
            default:
597
                throw new Exception("This type of network block is unknown!");
598
        }
599
        $retval = "<dict>";
600
        $retval .= $this->eapBlock($eapType);
601
        $retval .= "<key>EncryptionType</key>
602
                  <string>$encryptionTypeString</string>
603
               <key>HIDDEN_NETWORK</key>
604
                  <false />
605
               <key>PayloadDescription</key>
606
                  <string>$payloadName</string>
607
               <key>PayloadDisplayName</key>
608
                  <string>$payloadShortName</string>
609
               <key>PayloadIdentifier</key>
610
                  <string>" . self::IPHONE_PAYLOAD_PREFIX . ".$this->massagedConsortium.$this->massagedCountry.$this->massagedInst.$this->massagedProfile.$this->lang.$payloadIdentifier</string>
611
               <key>PayloadOrganization</key>
612
                  <string>" . $this->massagedConsortium . ".1x-config.org</string>
613
               <key>PayloadType</key>
614
                  <string>com.apple." . ($blocktype == MobileconfigSuperclass::NETWORK_BLOCK_TYPE_WIRED ? "firstactiveethernet" : "wifi") . ".managed</string>";
615
        $retval .= $this->proxySettings();
616
        $retval .= $setupModesString;
617
        if ($eapType['INNER'] == \core\common\EAP::NE_SILVERBULLET) {
618
            if ($this->clientCertUUID === NULL) {
619
                throw new Exception("Silverbullet REQUIRES a client certificate and we need to know the UUID!");
620
            }
621
            $retval .= "<key>PayloadCertificateUUID</key>
622
                        <string>$this->clientCertUUID</string>";
623
        }
624
        $retval .= "
625
               <key>PayloadUUID</key>
626
                  <string>" . \core\common\Entity::uuid() . "</string>
627
               <key>PayloadVersion</key>
628
                  <integer>1</integer>
629
                  $wifiNetworkIdentification</dict>";
630
        $this->serial = $this->serial + 1;
631
        \core\common\Entity::outOfThePotatoes();
632
        return $retval;
633
    }
634
635
    /**
636
     * Produces a Network block which sets a network to manual join (we don't
637
     * get any closer to removing a network in mobileconfig)
638
     * 
639
     * @param string $ssid the SSID to set to manual join only
640
     * @return string
641
     */
642
    private function removenetworkBlock($ssid)
643
    {
644
        \core\common\Entity::intoThePotatoes();
645
        $retval = "
646
<dict>
647
	<key>AutoJoin</key>
648
	<false/>
649
	<key>EncryptionType</key>
650
	<string>None</string>
651
	<key>HIDDEN_NETWORK</key>
652
	<false/>
653
	<key>IsHotspot</key>
654
	<false/>
655
	<key>PayloadDescription</key>
656
	<string>" . sprintf(_("This SSID should not be used after bootstrapping %s"), \config\ConfAssistant::CONSORTIUM['display_name']) . "</string>
657
	<key>PayloadDisplayName</key>
658
	<string>" . _("Disabled WiFi network") . "</string>
659
	<key>PayloadIdentifier</key>
660
	<string>" . self::IPHONE_PAYLOAD_PREFIX . ".$this->massagedConsortium.$this->massagedCountry.$this->massagedInst.$this->massagedProfile.$this->lang.wifi.disabled.$this->removeSerial</string>
661
	<key>PayloadType</key>
662
	<string>com.apple.wifi.managed</string>
663
	<key>PayloadUUID</key>
664
	<string>" . \core\common\Entity::uuid() . "</string>
665
	<key>PayloadVersion</key>
666
	<real>1</real>";
667
        $retval .= $this->proxySettings();
668
        $retval .= "<key>SSID_STR</key>
669
	<string>$ssid</string>
670
</dict>
671
";
672
        \core\common\Entity::outOfThePotatoes();
673
        return $retval;
674
    }
675
676
    const NETWORK_BLOCK_TYPE_SSID = 100;
677
    const NETWORK_BLOCK_TYPE_CONSORTIUMOIS = 101;
678
    const NETWORK_BLOCK_TYPE_WIRED = 102;
679
680
    /**
681
     * produces the entire series of Network blocks; all for SSID-based, 
682
     * Passpoint-based, wired, and manual-select only SSIDs
683
     * 
684
     * @return string
685
     */
686
    private function allNetworkBlocks()
687
    {
688
        $retval = "";
689
        $this->serial = 0;
690
        $this->loggerInstance->debug(3, $this->attributes['internal:networks'], "NETWORKS:\n", "\n");
691
        foreach ($this->attributes['internal:networks'] as $netName => $netDefinition) {
692
            // mobileconfig network blocks can only hold one SSID each, so iterate here
693
            foreach ($netDefinition['ssid'] as $ssid) {
694
                $retval .= $this->networkBlock(MobileconfigSuperclass::NETWORK_BLOCK_TYPE_SSID, $ssid, $netName);
695
            }
696
            // mobileconfig network blocks can accommodate a list of RCOIs in one statement, so just call it
697
            if ($this->selectedEapObject->isPasswordRequired() === FALSE || $netName != 'eduroam') {
698
                $retval .= $this->networkBlock(MobileconfigSuperclass::NETWORK_BLOCK_TYPE_CONSORTIUMOIS, $netDefinition['oi'], $netName);
699
            }
700
        }
701
        if (isset($this->attributes['media:wired']) && get_class($this) == "devices\apple_mobileconfig\DeviceMobileconfigOsX") {
702
            $retval .= $this->networkBlock(MobileconfigSuperclass::NETWORK_BLOCK_TYPE_WIRED, TRUE, \config\ConfAssistant::CONSORTIUM['display_name']);
703
        }
704
        if (isset($this->attributes['media:remove_SSID'])) {
705
            $this->removeSerial = 0;
706
            foreach ($this->attributes['media:remove_SSID'] as $removeSSID) {
707
                $retval .= $this->removenetworkBlock($removeSSID);
708
                $this->removeSerial = $this->removeSerial + 1;
709
            }
710
        }
711
        return $retval;
712
    }
713
714
    /**
715
     * collates a block with all CAs that are to be included in the mobileconfig
716
     * 
717
     * @return string
718
     */
719
    private function allCA()
720
    {
721
        $retval = "";
722
        $this->caSerial = 0;
723
        foreach ($this->attributes['internal:CAs'][0] as $ca) {
724
            $retval .= $this->caBlob($ca);
725
            $this->caSerial = $this->caSerial + 1;
726
        }
727
        return $retval;
728
    }
729
730
    /**
731
     * creates a Cert block containing a client certificate (used in SB only)
732
     * @return array the block itself, and the UUID of the certificate
733
     * @throws Exception
734
     */
735
    private function clientP12Block()
736
    {
737
        \core\common\Entity::intoThePotatoes();
738
        if (count($this->clientCert) == 0) {
739
            throw new Exception("the client block was called but there is no client certificate!");
740
        }
741
        $binaryBlob = $this->clientCert["certdata_nointermediate"];
742
        $mimeBlob = base64_encode($binaryBlob);
743
        $mimeFormatted = chunk_split($mimeBlob, 52, "\r\n");
744
        $payloadUUID = \core\common\Entity::uuid('', $mimeBlob);
745
        $retArray = ["block" => "<dict>" .
746
            // we don't include the import password. It's displayed on screen, and should be input by the user.
747
            // <key>Password</key>
748
            //   <string>" . $this->clientCert['password'] . "</string>
749
            "<key>PayloadCertificateFileName</key>
750
                     <string>" . $this->massagedConsortium . ".pfx</string>
751
                  <key>PayloadContent</key>
752
                     <data>
753
$mimeFormatted
754
                     </data>
755
                  <key>PayloadDescription</key>
756
                     <string>MIME Base-64 encoded PKCS#12 Client Certificate</string>
757
                  <key>PayloadDisplayName</key>
758
                     <string>" . _("User certificate") . "</string>
759
                  <key>PayloadIdentifier</key>
760
                     <string>com.apple.security.pkcs12.$payloadUUID</string>
761
                  <key>PayloadType</key>
762
                     <string>com.apple.security.pkcs12</string>
763
                  <key>PayloadUUID</key>
764
                     <string>$payloadUUID</string>
765
                  <key>PayloadVersion</key>
766
                     <integer>1</integer>
767
                </dict>",
768
            "UUID" => $payloadUUID,];
769
        \core\common\Entity::outOfThePotatoes();
770
        return $retArray;
771
    }
772
773
    /**
774
     * creates an Expiry block. This is only done in SB; the profile expires
775
     * when the client cert expires.
776
     * 
777
     * @return string
778
     * @throws Exception
779
     */
780
    private function expiryBlock() {
781
        if (count($this->clientCert) == 0) {
782
            throw new Exception("the expiry block was called but there is no client certificate!");
783
        }
784
        $expiryTime = new \DateTime($this->clientCert['certObject']->expiry);
785
        return "<key>RemovalDate</key>
786
        <date>" . $expiryTime->format("Y-m-d") . "T" . $expiryTime->format("H:i:s") . "Z</date>";
787
    }
788
789
    /**
790
     * creates a block for one single CA
791
     * 
792
     * @param array $ca the CA for which to generate the XML block
793
     * @return string
794
     */
795
    private function caBlob($ca)
796
    {
797
        \core\common\Entity::intoThePotatoes();
798
        $stream = "";
799
        if (!in_array($ca['uuid'], $this->CAsAccountedFor)) { // skip if this is a duplicate
800
            // cut lines with CERTIFICATE
801
            $stage1 = preg_replace('/-----BEGIN CERTIFICATE-----/', '', $ca['pem']);
802
            $stage2 = preg_replace('/-----END CERTIFICATE-----/', '', $stage1);
803
            $trimmedPem = trim($stage2);
804
805
            $stream = "
806
            <dict>
807
               <key>PayloadCertificateFileName</key>
808
               <string>" . $ca['uuid'] . ".der</string>
809
               <key>PayloadContent</key>
810
               <data>
811
" . $trimmedPem . "</data>
812
               <key>PayloadDescription</key>
813
               <string>" . sprintf(_("The %s Certification Authority"), \core\common\Entity::$nomenclature_idp) . "</string>
814
               <key>PayloadDisplayName</key>
815
               <string>" . 
816
                    /// example: "Identity Provider CA #1 (Root)"
817
                    sprintf(_("%s CA #%d (%s)" ), 
818
                            \core\common\Entity::$nomenclature_idp, 
819
                            count($this->CAsAccountedFor)+1, 
820
                            ($ca['root'] ? _("Root") : _("Intermediate"))) . 
821
              "</string>
822
               <key>PayloadIdentifier</key>
823
               <string>" . self::IPHONE_PAYLOAD_PREFIX . ".$this->massagedConsortium.$this->massagedCountry.$this->massagedInst.$this->massagedProfile.credential.$this->caSerial</string>
824
               <key>PayloadOrganization</key>
825
               <string>" . $this->massagedConsortium . ".1x-config.org</string>
826
               <key>PayloadType</key>
827
               <string>com.apple.security.root</string>
828
               <key>PayloadUUID</key><string>" . $ca['uuid'] . "</string>
829
               <key>PayloadVersion</key>
830
               <integer>1</integer>
831
            </dict>";
832
            $this->CAsAccountedFor[] = $ca['uuid'];
833
        }
834
        \core\common\Entity::outOfThePotatoes();
835
        return $stream;
836
    }
837
838
}
839