Passed
Branch master (eaa143)
by Stefan
04:52
created

mobileconfigSuperclass::generalPayload()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 25
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 11
nc 4
nop 0
dl 0
loc 25
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * ******************************************************************************
5
 * Copyright 2011-2017 DANTE Ltd. and GÉANT on behalf of the GN3, GN3+, GN4-1 
6
 * and GN4-2 consortia
7
 *
8
 * License: see the web/copyright.php file in the file structure
9
 * ******************************************************************************
10
 */
11
12
/**
13
 * This file contains the installer for iOS devices and Apple 10.7 Lion
14
 *
15
 *
16
 * @author Stefan Winter <[email protected]>
17
 * @package Developer
18
 */
19
/**
20
 * 
21
 */
22
23
namespace devices\apple_mobileconfig;
24
25
use \Exception;
26
27
/**
28
 * This is the main implementation class of the module
29
 *
30
 * The class should only define one public method: writeInstaller.
31
 *
32
 * All other methods and properties should be private. This example sets zipInstaller method to protected, so that it can be seen in the documentation.
33
 *
34
 * @package Developer
35
 */
36
abstract class mobileconfigSuperclass extends \core\DeviceConfig {
37
38
    private $instName;
39
    private $profileName;
40
    private $massagedInst;
41
    private $massagedProfile;
42
    private $massagedCountry;
43
    private $massagedConsortium;
44
    private $lang;
45
    static private $iPhonePayloadPrefix = "org.1x-config";
46
47
    public function __construct() {
48
        parent::__construct();
49
        // that's what all variants support. Sub-classes can change it.
50
        $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]);
51
        $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.");
52
    }
53
54
    private function massageName($input) {
55
        return htmlspecialchars(strtolower(iconv("UTF-8", "US-ASCII//TRANSLIT", preg_replace(['/ /', '/\//'], '_', $input))), ENT_XML1, 'UTF-8');
56
    }
57
58
    private function generalPayload() {
59
        $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']);
60
61
        $eapType = $this->selectedEap;
62
        // simpler message for silverbullet
63
        if ($eapType['INNER'] == \core\common\EAP::NE_SILVERBULLET) {
64
            $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']);
65
        }
66
67
        return "
68
      <key>PayloadDescription</key>
69
         <string>$tagline</string>
70
      <key>PayloadDisplayName</key>
71
         <string>" . CONFIG_CONFASSISTANT['CONSORTIUM']['display_name'] . "</string>
72
      <key>PayloadIdentifier</key>
73
         <string>" . self::$iPhonePayloadPrefix . ".$this->massagedConsortium.$this->massagedCountry.$this->massagedInst.$this->massagedProfile.$this->lang</string>
74
      <key>PayloadOrganization</key>
75
         <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>
76
      <key>PayloadType</key>
77
         <string>Configuration</string>
78
      <key>PayloadUUID</key>
79
         <string>" . $this->uuid('', self::$iPhonePayloadPrefix . $this->massagedConsortium . $this->massagedCountry . $this->massagedInst . $this->massagedProfile) . "</string>
80
      <key>PayloadVersion</key>
81
         <integer>1</integer>";
82
    }
83
84
    const FILE_START = "<?xml version=\"1.0\" encoding=\"utf-8\"?>
85
<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\"
86
\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
87
<plist version=\"1.0\">
88
<dict>";
89
    const FILE_END = "</dict></plist>";
90
    const BUFFER_CONSENT_PRE = "
91
      <key>ConsentText</key>
92
         <dict>
93
            <key>default</key>
94
               <string>";
95
    const BUFFER_CONSENT_POST = "</string>
96
         </dict>
97
         ";
98
99
    protected function consentBlock() {
100
        if (isset($this->attributes['support:info_file'])) {
101
            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;
102
        }
103
        if (isset($this->attributes['internal:verify_userinput_suffix'])) {
104
            if (isset($this->attributes['internal:realm'])) {
105
                return mobileconfigSuperclass::BUFFER_CONSENT_PRE . sprintf(_("Important Notice: your username must end with @%s!"), $this->attributes['internal:realm'][0]) . mobileconfigSuperclass::BUFFER_CONSENT_POST;
106
            }
107
            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;
108
        }
109
        return "";
110
    }
111
112
    /**
113
     * prepare a zip archive containing files and settings which normally would be used inside the module to produce an installer
114
     *
115
     * {@source}
116
     */
117
    public function writeInstaller() {
118
        /** run innitial setup
119
          this will:
120
          - create the temporary directory and save its path as $this->FPATH
121
          - process the CA certificates and store results in $this->attributes['internal:CAs'][0]
122
          $this->attributes['internal:CAs'][0] is an array of processed CA certificates
123
          a processed certifincate is an array
124
          'pem' points to pem feromat certificate
125
          'der' points to der format certificate
126
          'md5' points to md5 fingerprint
127
          'sha1' points to sha1 fingerprint
128
          'name' points to the certificate subject
129
          'root' can be 1 for self-signed certificate or 0 otherwise
130
131
          - save the info_file (if exists) and put the name in $this->attributes['internal:info_file_name'][0]
132
         */
133
        $dom = textdomain(NULL);
134
        textdomain("devices");
135
136
        $this->loggerInstance->debug(4, "mobileconfig Module Installer start\n");
137
138
        // remove spaces and slashes (filename!), make sure it's simple ASCII only, then lowercase it
139
        // also escape htmlspecialchars
140
        // not all names and profiles have a name, so be prepared
141
142
        $this->loggerInstance->debug(5, "List of available attributes: " . var_export($this->attributes, TRUE));
143
144
        $this->instName = $this->attributes['general:instname'][0] ?? sprintf(_("Unnamed %s"), $this->nomenclature_inst);
145
        $this->profileName = $this->attributes['profile:name'][0] ?? _("Unnamed Profile");
146
147
        $this->massagedInst = $this->massageName($this->instName);
148
        $this->massagedProfile = $this->massageName($this->profileName);
149
        $this->massagedCountry = $this->massageName($this->attributes['internal:country'][0]);
150
        $this->massagedConsortium = $this->massageName(CONFIG_CONFASSISTANT['CONSORTIUM']['name']);
151
        $this->lang = preg_replace('/\..+/', '', setlocale(LC_ALL, "0"));
152
153
        $eapType = $this->selectedEap;
154
155
        $outputXml = self::FILE_START;
156
        $outputXml .= "<key>PayloadContent</key>
157
         <array>";
158
159
        // did the admin want wired config?
160
        $includeWired = FALSE;
161
        if (isset($this->attributes['media:wired']) && get_class($this) == "Device_mobileconfig_os_x") {
162
            $includeWired = TRUE;
163
        }
164
165
        // if we are in silverbullet, we will need a whole own block for the client credential
166
        // and also for the profile expiry
167
168
        $clientCertUUID = NULL;
169
        if ($eapType['INNER'] == \core\common\EAP::NE_SILVERBULLET) {
170
            $blockinfo = $this->clientP12Block();
171
            $outputXml .= $blockinfo['block'];
172
            $clientCertUUID = $blockinfo['UUID'];
173
        }
174
175
        $outputXml .= $this->allCA($this->attributes['internal:CAs'][0]);
176
177
        $outputXml .= $this->allNetworkBlocks(
178
                $this->attributes['internal:SSID'], $this->attributes['internal:consortia'], $this->attributes['eap:server_name'], $this->listCAUuids($this->attributes['internal:CAs'][0]), $this->selectedEap, $includeWired, $clientCertUUID, $this->determineOuterIdString());
179
180
        $outputXml .= "</array>";
181
        $outputXml .= $this->generalPayload();
182
        $outputXml .= $this->consentBlock();
183
184
        if ($eapType['INNER'] == \core\common\EAP::NE_SILVERBULLET) {
185
            $outputXml .= $this->expiryBlock();
186
        }
187
        $outputXml .= self::FILE_END;
188
189
        file_put_contents('installer_profile', $outputXml);
190
191
        textdomain($dom);
192
193
        $fileName = $this->installerBasename . '.mobileconfig';
194
195
        if (!$this->sign) {
196
            rename("installer_profile", $fileName);
197
            return $fileName;
198
        }
199
        // still here? Then we are signing.
200
        $signing = system($this->sign . " installer_profile '$fileName' > /dev/null");
201
        if ($signing === FALSE) {
202
            $this->loggerInstance->debug(2, "Signing the mobileconfig installer $fileName FAILED!\n");
203
        }
204
        return $fileName;
205
    }
206
207
    public function writeDeviceInfo() {
208
        $ssidCount = count($this->attributes['internal:SSID']);
209
        $certCount = count($this->attributes['internal:CAs'][0]);
210
        $out = "<p>" . _("For best results, please use the built-in browser (Safari) to open the configuration file.") . "</p>";
211
        $out .= "<p>";
212
        $out .= _("The profile will install itself after you click (or tap) the button. You will be asked for confirmation/input at several points:");
213
        $out .= "<ul>";
214
        $out .= "<li>" . _("to install the profile") . "</li>";
215
        $out .= "<li>" . ngettext("to accept the server certificate authority", "to accept the server certificate authorities", $certCount);
216
        if ($certCount > 1) {
217
            $out .= " " . sprintf(_("(%d times)"), $certCount);
218
        }
219
        $out .= "</li>";
220
        $out .= "<li>" . sprintf(_("to enter the username and password of your %s"), $this->nomenclature_inst);
221
        if ($ssidCount > 1) {
222
            $out .= " " . sprintf(_("(%d times each, because %s is installed for %d SSIDs)"), $ssidCount, CONFIG_CONFASSISTANT['CONSORTIUM']['display_name'], $ssidCount);
223
        }
224
        $out .= "</li>";
225
        $out .= "</ul>";
226
        $out .= "</p>";
227
        return $out;
228
    }
229
230
    private function listCAUuids($caArray) {
231
        $retval = [];
232
        foreach ($caArray as $ca) {
233
            $retval[] = $ca['uuid'];
234
        }
235
        return $retval;
236
    }
237
238
    private function passPointBlock($consortiumOi) {
239
        $retval = "
240
               <key>IsHotspot</key>
241
               <true/>
242
               <key>ServiceProviderRoamingEnabled</key>
243
               <true/>
244
               <key>DisplayedOperatorName</key>
245
               <string>" . CONFIG_CONFASSISTANT['CONSORTIUM']['display_name'] . " via Passpoint</string>";
246
        // if we don't know the realm, omit the entire DomainName key
247
        if (isset($this->attributes['internal:realm'])) {
248
            $retval .= "<key>DomainName</key>
249
               <string>";
250
            $retval .= $this->attributes['internal:realm'][0];
251
            $retval .= "</string>
252
                ";
253
        }
254
        $retval .= "                <key>RoamingConsortiumOIs</key>
255
                <array>";
256
        foreach ($consortiumOi as $oiValue) {
257
            $retval .= "<string>$oiValue</string>";
258
        }
259
        $retval .= "</array>";
260
        // this is an undocmented value found on the net. Does it do something useful?
261
        $retval .= "<key>_UsingHotspot20</key>
262
                <true/>
263
                ";
264
        // do we need to set NAIRealmName ? In Rel 1, probably yes, in Rel 2, 
265
        // no because ConsortiumOI is enough.
266
        // but which release is OS X doing? And what should we fill in, given
267
        // that we have thousands of realms? Try just eduroam.org
268
        if (CONFIG_CONFASSISTANT['CONSORTIUM']['name'] == "eduroam") {
269
            $retval .= "<key>NAIRealmNames</key>
270
                <array>
271
                    <string>eduroam.org</string>
272
                </array>";
273
        }
274
        return $retval;
275
    }
276
277
    private $serial;
278
279
    private function eapBlock($eapType, $realm, $cAUUIDList, $serverList) {
280
        $retval = "<key>EAPClientConfiguration</key>
281
                  <dict>
282
                      <key>AcceptEAPTypes</key>
283
                         <array>
284
                            <integer>" . $eapType['OUTER'] . "</integer>
285
                         </array>
286
                      <key>EAPFASTProvisionPAC</key>
287
                            <true />
288
                      <key>EAPFASTUsePAC</key>
289
                            <true />
290
                      <key>EAPFastProvisionPACAnonymously</key>
291
                            <false />
292
                      <key>OneTimeUserPassword</key>
293
                            <false />
294
";
295
        if ($realm !== 0) {
296
            $retval .= "<key>OuterIdentity</key>
297
                                    <string>" . htmlspecialchars($realm, ENT_XML1, 'UTF-8') . "</string>
298
";
299
        }
300
        $retval .= "<key>PayloadCertificateAnchorUUID</key>
301
                         <array>";
302
        foreach ($cAUUIDList as $uuid) {
303
            $retval .= "
304
<string>$uuid</string>";
305
        }
306
        $retval .= "
307
                         </array>
308
                      <key>TLSAllowTrustExceptions</key>
309
                         <false />
310
                      <key>TLSTrustedServerNames</key>
311
                         <array>";
312
        foreach ($serverList as $commonName) {
313
            $retval .= "
314
<string>$commonName</string>";
315
        }
316
        $retval .= "
317
                         </array>";
318
        if ($eapType['INNER'] == \core\common\EAP::NE_SILVERBULLET) {
319
            $retval .= "<key>UserName</key><string>" . $this->clientCert["username"] . "</string>";
320
        }
321
        $retval .= "
322
                      <key>TTLSInnerAuthentication</key>
323
                         <string>" . ($eapType['INNER'] == \core\common\EAP::NONE ? "PAP" : "MSCHAPv2") . "</string>
324
                   </dict>";
325
        return $retval;
326
    }
327
328
    private function networkBlock($ssid, $consortiumOi, $serverList, $cAUUIDList, $eapType, $wired, $clientCertUUID, $realm = 0) {
329
        $escapedSSID = htmlspecialchars($ssid, ENT_XML1, 'UTF-8');
330
331
        $payloadIdentifier = "wifi." . $this->serial;
332
        $payloadShortName = sprintf(_("SSID %s"), $escapedSSID);
333
        $payloadName = sprintf(_("%s configuration for network name %s"), CONFIG_CONFASSISTANT['CONSORTIUM']['display_name'], $escapedSSID);
334
        $encryptionTypeString = "WPA";
335
        $setupModesString = "";
336
        $wifiNetworkIdentification = "<key>SSID_STR</key>
337
                  <string>$escapedSSID</string>";
338
339 View Code Duplication
        if ($wired) { // override the above defaults for wired interfaces
340
            $payloadIdentifier = "firstactiveethernet";
341
            $payloadShortName = _("Wired Network");
342
            $payloadName = sprintf(_("%s configuration for wired network"), CONFIG_CONFASSISTANT['CONSORTIUM']['display_name']);
343
            $encryptionTypeString = "any";
344
            $setupModesString = "
345
               <key>SetupModes</key>
346
                  <array>
347
                     <string>System</string>
348
                  </array>";
349
            $wifiNetworkIdentification = "";
350
        }
351
352 View Code Duplication
        if (count($consortiumOi) > 0) { // override the above defaults for HS20 configuration
353
            $payloadIdentifier = "hs20";
354
            $payloadShortName = _("Hotspot 2.0 Settings");
355
            $payloadName = sprintf(_("%s Hotspot 2.0 configuration"), CONFIG_CONFASSISTANT['CONSORTIUM']['display_name']);
356
            $encryptionTypeString = "WPA";
357
            $wifiNetworkIdentification = $this->passPointBlock($consortiumOi);
358
        }
359
360
        $retval = "<dict>";
361
        $retval .= $this->eapBlock($eapType, $realm, $cAUUIDList, $serverList);
362
        $retval .= "<key>EncryptionType</key>
363
                  <string>$encryptionTypeString</string>
364
               <key>HIDDEN_NETWORK</key>
365
                  <true />
366
               <key>PayloadDescription</key>
367
                  <string>$payloadName</string>
368
               <key>PayloadDisplayName</key>
369
                  <string>$payloadShortName</string>
370
               <key>PayloadIdentifier</key>
371
                  <string>" . self::$iPhonePayloadPrefix . ".$this->massagedConsortium.$this->massagedCountry.$this->massagedInst.$this->massagedProfile.$this->lang.$payloadIdentifier</string>
372
               <key>PayloadOrganization</key>
373
                  <string>" . $this->massagedConsortium . ".1x-config.org</string>
374
               <key>PayloadType</key>
375
                  <string>com.apple." . ($wired ? "firstactiveethernet" : "wifi") . ".managed</string>";
376
        $this->loggerInstance->debug(2, get_class($this));
377
        if (get_class($this) != "Device_mobileconfig_ios_56") {
378
            $retval .= "<key>ProxyType</key>
379
                  <string>Auto</string>
380
                  <key>ProxyPACFallbackAllowed</key>
381
                  <true/>
382
                ";
383
        }
384
        $retval .= $setupModesString;
385
        if ($eapType['INNER'] == \core\common\EAP::NE_SILVERBULLET) {
386
            if ($clientCertUUID === NULL) {
387
                throw new Exception("Silverbullet REQUIRES a client certificate and we need to know the UUID!");
388
            }
389
            $retval .= "<key>PayloadCertificateUUID</key>
390
                        <string>$clientCertUUID</string>";
391
        }
392
        $retval .= "
393
               <key>PayloadUUID</key>
394
                  <string>" . $this->uuid() . "</string>
395
               <key>PayloadVersion</key>
396
                  <integer>1</integer>
397
                  $wifiNetworkIdentification</dict>";
398
        $this->serial = $this->serial + 1;
399
        return $retval;
400
    }
401
402
    private function removenetworkBlock($ssid, $sequence) {
403
        $retval = "
404
<dict>
405
	<key>AutoJoin</key>
406
	<false/>
407
	<key>EncryptionType</key>
408
	<string>None</string>
409
	<key>HIDDEN_NETWORK</key>
410
	<false/>
411
	<key>IsHotspot</key>
412
	<false/>
413
	<key>PayloadDescription</key>
414
	<string>" . sprintf(_("This SSID should not be used after bootstrapping %s"), CONFIG_CONFASSISTANT['CONSORTIUM']['display_name']) . "</string>
415
	<key>PayloadDisplayName</key>
416
	<string>" . _("Disabled WiFi network") . "</string>
417
	<key>PayloadIdentifier</key>
418
	<string>" . self::$iPhonePayloadPrefix . ".$this->massagedConsortium.$this->massagedCountry.$this->massagedInst.$this->massagedProfile.$this->lang.wifi.disabled.$sequence</string>
419
	<key>PayloadType</key>
420
	<string>com.apple.wifi.managed</string>
421
	<key>PayloadUUID</key>
422
	<string>" . $this->uuid() . "</string>
423
	<key>PayloadVersion</key>
424
	<real>1</real>";
425
        if (get_class($this) != "Device_mobileconfig_ios_56") {
426
            $retval .= "<key>ProxyType</key>
427
	<string>Auto</string>";
428
        }
429
        $retval .= "<key>SSID_STR</key>
430
	<string>$ssid</string>
431
</dict>
432
";
433
        return $retval;
434
    }
435
436
    private function allNetworkBlocks($sSIDList, $consortiumOIList, $serverNameList, $cAUUIDList, $eapType, $includeWired, $clientcertUUID, $realm = 0) {
437
        $retval = "";
438
        $this->serial = 0;
439
        foreach (array_keys($sSIDList) as $ssid) {
440
            $retval .= $this->networkBlock($ssid, NULL, $serverNameList, $cAUUIDList, $eapType, FALSE, $clientcertUUID, $realm);
441
        }
442 View Code Duplication
        if ($includeWired) {
443
            $retval .= $this->networkBlock("IRRELEVANT", NULL, $serverNameList, $cAUUIDList, $eapType, TRUE, $clientcertUUID, $realm);
444
        }
445 View Code Duplication
        if (count($consortiumOIList) > 0) {
446
            $retval .= $this->networkBlock("IRRELEVANT", $consortiumOIList, $serverNameList, $cAUUIDList, $eapType, FALSE, $clientcertUUID, $realm);
447
        }
448
        if (isset($this->attributes['media:remove_SSID'])) {
449
            foreach ($this->attributes['media:remove_SSID'] as $index => $removeSSID) {
450
                $retval .= $this->removenetworkBlock($removeSSID, $index);
451
            }
452
        }
453
        return $retval;
454
    }
455
456
    private function allCA($caArray) {
457
        $retval = "";
458
        $iterator = 0;
459
        foreach ($caArray as $ca) {
460
            $retval .= $this->caBlob($ca['uuid'], $ca['pem'], $iterator);
461
            $iterator = $iterator + 1;
462
        }
463
        return $retval;
464
    }
465
466
    private function clientP12Block() {
467
        if (!is_array($this->clientCert)) {
468
            throw new Exception("the client block was called but there is no client certificate!");
469
        }
470
        $binaryBlob = $this->clientCert["certdata"];
471
        $mimeBlob = base64_encode($binaryBlob);
472
        $mimeFormatted = chunk_split($mimeBlob, 52, "\r\n");
473
        $payloadUUID = $this->uuid('', $mimeBlob);
474
        return ["block" => "<dict>" .
475
            // we don't include the import password. It's displayed on screen, and should be input by the user.
476
            // <key>Password</key>
477
            //   <string>" . $this->clientCert['password'] . "</string>
478
            "<key>PayloadCertificateFileName</key>
479
                     <string>cert-cli.pfx</string>
480
                  <key>PayloadContent</key>
481
                     <data>
482
$mimeFormatted
483
                     </data>
484
                  <key>PayloadDescription</key>
485
                     <string>MIME Base-64 encoded PKCS#12 Client Certificate</string>
486
                  <key>PayloadDisplayName</key>
487
                     <string>" . _("eduroam user certificate") . "</string>
488
                  <key>PayloadIdentifier</key>
489
                     <string>com.apple.security.pkcs12.$payloadUUID</string>
490
                  <key>PayloadType</key>
491
                     <string>com.apple.security.pkcs12</string>
492
                  <key>PayloadUUID</key>
493
                     <string>$payloadUUID</string>
494
                  <key>PayloadVersion</key>
495
                     <integer>1</integer>
496
                </dict>",
497
            "UUID" => $payloadUUID,];
498
    }
499
500
    private function expiryBlock() {
501
        if (!is_array($this->clientCert)) {
502
            throw new Exception("the expiry block was called but there is no client certificate!");
503
        }
504
        $expiryTime = $this->clientCert['expiry'];
505
        return "<key>RemovalDate</key>
506
        <date>$expiryTime</date>";
507
    }
508
509
    private function caBlob($uuid, $pem, $serial) {
510
        // cut lines with CERTIFICATE
511
        $stage1 = preg_replace('/-----BEGIN CERTIFICATE-----/', '', $pem);
512
        $stage2 = preg_replace('/-----END CERTIFICATE-----/', '', $stage1);
513
        $trimmedPem = trim($stage2);
514
515
        $stream = "
516
            <dict>
517
               <key>PayloadCertificateFileName</key>
518
               <string>$uuid.der</string>
519
               <key>PayloadContent</key>
520
               <data>
521
" . $trimmedPem . "</data>
522
               <key>PayloadDescription</key>
523
               <string>" . _("Your Identity Providers Certification Authority") . "</string>
524
               <key>PayloadDisplayName</key>
525
               <string>" . _("Identity Provider's CA") . "</string>
526
               <key>PayloadIdentifier</key>
527
               <string>" . self::$iPhonePayloadPrefix . ".$this->massagedConsortium.$this->massagedCountry.$this->massagedInst.$this->massagedProfile.credential.$serial</string>
528
               <key>PayloadOrganization</key>
529
               <string>" . $this->massagedConsortium . ".1x-config.org</string>
530
               <key>PayloadType</key>
531
               <string>com.apple.security.root</string>
532
               <key>PayloadUUID</key><string>" . $uuid . "</string>
533
               <key>PayloadVersion</key>
534
               <integer>1</integer>
535
            </dict>";
536
537
        return $stream;
538
    }
539
540
}
541