CertificationAuthorityEduPkiServer   C
last analyzed

Complexity

Total Complexity 57

Size/Duplication

Total Lines 522
Duplicated Lines 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
wmc 57
eloc 282
c 5
b 1
f 0
dl 0
loc 522
rs 5.04

12 Methods

Rating   Name   Duplication   Size   Complexity  
A triggerNewOCSPStatement() 0 4 1
A signRequest() 0 14 3
A __construct() 0 38 5
B pickupFinalCert() 0 48 11
A generateCompatiblePrivateKey() 0 7 3
B revokeCertificate() 0 41 7
A generateCompatibleCsr() 0 30 3
A soapFromXmlInteger() 0 6 1
A soapToXmlInteger() 0 5 1
A updateFreshness() 0 2 1
F sendRequestToCa() 0 135 17
A initEduPKISoapSession() 0 59 4

How to fix   Complexity   

Complex Class

Complex classes like CertificationAuthorityEduPkiServer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CertificationAuthorityEduPkiServer, and based on these observations, apply Extract Interface, too.

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
namespace core;
13
14
use \Exception;
0 ignored issues
show
Bug introduced by
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...
15
use \SoapFault;
0 ignored issues
show
Bug introduced by
The type \SoapFault 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...
16
17
class CertificationAuthorityEduPkiServer extends EntityWithDBProperties implements CertificationAuthorityInterface
18
{
19
    private $locationRaCert;
20
    private $locationRaKey;
21
    private $locationWebRoot;
22
    private $eduPkiRaId;
23
    private $eduPkiCertProfileBoth;
24
    private $eduPkiCertProfileIdp;
25
    private $eduPkiCertProfileSp;
26
    private $eduPkiRaPkeyPassphrase;
27
    private $eduPkiEndpointPublic;
28
    private $eduPkiEndpointRa;
29
30
    /**
31
     * sets up the environment so that we can talk to eduPKI
32
     * 
33
     * @throws Exception
34
     */
35
    public function __construct()
36
    {
37
            
38
        if ( \config\ConfAssistant::eduPKI['testing'] === true ) {
39
            $this->locationRaCert = ROOT . "/config/SilverbulletClientCerts/edupki-test-ra.pem";
40
            $this->locationRaKey = ROOT . "/config/SilverbulletClientCerts/edupki-test-ra.clearkey";
41
            $this->locationWebRoot = ROOT . "/config/SilverbulletClientCerts/eduPKI-webserver-root.pem";
42
            $this->eduPkiRaId = 700;
43
            $this->eduPkiCertProfileBoth = "Radius Server SOAP";
44
            $this->eduPkiCertProfileIdp = "Radius Server SOAP";
45
            $this->eduPkiCertProfileSp = "Radius Server SOAP";
46
            $this->eduPkiRaPkeyPassphrase = "...";
47
            $this->eduPkiEndpointPublic = "https://pki.edupki.org/edupki-test-ca/cgi-bin/pub/soap?wsdl=1";
48
            $this->eduPkiEndpointRa = "https://ra.edupki.org/edupki-test-ca/cgi-bin/ra/soap?wsdl=1";
49
        } else {
50
            $this->locationRaCert = ROOT . "/config/SilverbulletClientCerts/edupki-prod-ra.pem";
51
            $this->locationRaKey = ROOT . "/config/SilverbulletClientCerts/edupki-prod-ra.clearkey";
52
            $this->locationWebRoot = ROOT . "/config/SilverbulletClientCerts/eduPKI-webserver-root.pem";
53
            $this->eduPkiRaId = 100;
54
            $this->eduPkiCertProfileBoth = "eduroam IdP and SP";
55
            $this->eduPkiCertProfileIdp = "eduroam IdP";
56
            $this->eduPkiCertProfileSp = "eduroam SP";
57
            $this->eduPkiRaPkeyPassphrase = "...";
58
            $this->eduPkiEndpointPublic = "https://pki.edupki.org/edupki-ca/cgi-bin/pub/soap?wsdl=1";
59
            $this->eduPkiEndpointRa = "https://ra.edupki.org/edupki-ca/cgi-bin/ra/soap?wsdl=1";        
60
        }
61
        
62
        $this->databaseType = "INST";
63
        parent::__construct();
64
65
        if (stat($this->locationRaCert) === FALSE) {
66
            throw new Exception("RA operator PEM file not found: " . $this->locationRaCert);
67
        }
68
        if (stat($this->locationRaKey) === FALSE) {
69
            throw new Exception("RA operator private key file not found: " . $this->locationRaKey);
70
        }
71
        if (stat($this->locationWebRoot) === FALSE) {
72
            throw new Exception("CA website root CA file not found: " . $this->locationWebRoot);
73
        }
74
    }
75
76
    /**
77
     * Creates an updated OCSP statement. Nothing to be done here - eduPKI have
78
     * their own OCSP responder and the certs point to it. So we are not in the 
79
     * loop.
80
     * 
81
     * @param string $serial serial number of the certificate. Serials are 128 bit, so forcibly a string.
82
     * @return string a dummy string instead of a real statement
83
     */
84
    public function triggerNewOCSPStatement($serial): string
85
    {
86
        unset($serial); // not needed
87
        return "EXTERNAL";
88
    }
89
90
    /**
91
     * signs a CSR and returns the certificate (blocking wait)
92
     * 
93
     * @param array  $csr        the request metadata
94
     * @param integer $expiryDays how many days should the certificate be valid
95
     * @return array the certificate with some meta info
96
     * @throws Exception
97
     */
98
    public function signRequest($csr, $expiryDays): array
99
    {
100
        if ($csr["CSR_STRING"] === NULL) {
101
            throw new Exception("This CA needs the CSR in a string (PEM)!");
102
        }
103
        $revocationPin = common\Entity::randomString(10, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
104
        $soapReqnum = $this->sendRequestToCa($csr, $revocationPin, $expiryDays);
105
        sleep(55);
106
        // now, get the actual cert from the CA
107
        $returnValue = $this->pickupFinalCert($soapReqnum, TRUE);
108
        if ($returnValue === FALSE) {
109
            throw new Exception("We wanted to wait, but still got no cert!");
110
        }
111
        return $returnValue;
112
    }
113
114
    /**
115
     * sends the request to the CA and asks for the certificate. Does not block
116
     * until the certificate is issued, it needs to be picked up separately
117
     * using its request number.
118
     * 
119
     * @param array  $csr           the CSR to sign. The member $csr['CSR'] must contain the CSR in *PEM* format
120
     * @param string $revocationPin a PIN to be able to revoke the cert later on
121
     * @param int    $expiryDays    how many days should the certificate be valid
122
     * @return int the request serial number
123
     * @throws Exception
124
     */
125
    public function sendRequestToCa($csr, $revocationPin, $expiryDays): int
126
    {
127
        // initialise connection to eduPKI CA / eduroam RA and send the request to them
128
        try {            
129
            if (in_array("eduroam IdP", $csr["POLICIES"]) && in_array("eduroam SP", $csr["POLICIES"])) {
130
                $profile = $this->eduPkiCertProfileBoth;
131
            } elseif (in_array("eduroam IdP", $csr["POLICIES"])) {
132
                $profile = $this->eduPkiCertProfileIdp;
133
            } elseif (in_array("eduroam SP", $csr["POLICIES"])) {
134
                $profile = $this->eduPkiCertProfileSp;
135
            } else {
136
                throw new Exception("Unexpected policies requested.");
137
            }
138
            $altArray = [# Array mit den Subject Alternative Names
139
                "email:" . $csr["USERMAIL"]
140
            ];
141
            foreach ($csr["ALTNAMES"] as $oneAltName) {
142
                if (!empty($oneAltName) && preg_match('/(?=^.{1,254}$)(^(?:(?!\d|-)[a-z0-9\-]{1,63}(?<!-)\.)+(?:[a-z]{2,})$)/i', $oneAltName) > 0) {
143
                    $altArray[] = "DNS:" . $oneAltName;
144
                } else {
145
                    $altArray[] = "IP:" . $oneAltName;
146
                }
147
            }
148
            $soapPub = $this->initEduPKISoapSession("PUBLIC");
149
            $this->loggerInstance->debug(5, "FIRST ACTUAL SOAP REQUEST (Public, newRequest)!\n");
150
            $this->loggerInstance->debug(5, "PARAM_1: " . $this->eduPkiRaId . "\n");
151
            $this->loggerInstance->debug(5, "PARAM_2: " . $csr["CSR_STRING"] . "\n");
152
            $this->loggerInstance->debug(5, "PARAM_3: ");
153
            $this->loggerInstance->debug(5, $altArray);
154
            $this->loggerInstance->debug(5, "PARAM_4: " . $profile . "\n");
155
            $this->loggerInstance->debug(5, "PARAM_5: " . sha1("notused") . "\n");
156
            $this->loggerInstance->debug(5, "PARAM_6: " . $csr["USERNAME"] . "\n");
157
            $this->loggerInstance->debug(5, "PARAM_7: " . $csr["USERMAIL"] . "\n");
158
            $this->loggerInstance->debug(5, "PARAM_8: " . ProfileSilverbullet::PRODUCTNAME . "\n");
159
            $this->loggerInstance->debug(5, "PARAM_9: false\n");
160
            
161
            $soapNewRequest = $soapPub->newRequest(
162
                    $this->eduPkiRaId, # RA-ID
163
                    $csr["CSR_STRING"], # Request im PEM-Format
164
                    $altArray, # altNames
165
                    $profile, # Zertifikatprofil
166
                    sha1($revocationPin), # PIN
167
                    $csr["USERNAME"], # Name des Antragstellers
168
                    $csr["USERMAIL"], # Kontakt-E-Mail
169
                    ProfileSilverbullet::PRODUCTNAME, # Organisationseinheit des Antragstellers
170
                    false                   # Veröffentlichen des Zertifikats?
171
            );
172
            $this->loggerInstance->debug(5, $soapPub->__getLastRequest());
173
            $this->loggerInstance->debug(5, $soapPub->__getLastResponse());
174
            if ($soapNewRequest == 0) {
175
                throw new Exception("Error when sending SOAP request (request serial number was zero). No further details available.");
176
            }
177
            $soapReqnum = intval($soapNewRequest);
178
        } catch (Exception $e) {
179
            // PHP 7.1 can do this much better
180
            if (is_soap_fault($e)) {
181
                $_SESSION['CSR_ERRORS'] = 'SOAP_ERROR';
182
                $_SESSION['csr_faultcode'] = $e->faultstring;
183
                #throw new Exception("Error when sending SOAP request: " . "{$e->faultcode}:  {
184
                #    $e->faultstring
185
                #}\n");
186
                return 0;
187
            }
188
            throw new Exception("Something odd happened while doing the SOAP request:" . $e->getMessage());
189
        }
190
        try {
191
            $soap = $this->initEduPKISoapSession("RA");
192
            // tell the CA the desired expiry date of the new certificate
193
            $expiry = new \DateTime();
194
            $expiry->modify("+$expiryDays day");
195
            $expiry->setTimezone(new \DateTimeZone("UTC"));
196
            $soapParameters = [
197
                "RaID" => $this->eduPkiRaId,
198
                "Role" => $profile,
199
                "Subject" => $csr['SUBJECT'],
200
                "SubjectAltNames" => $altArray,
201
                "NotBefore" => (new \DateTime())->format('c'),
202
                ];
203
            if (\config\ConfAssistant::eduPKI['max_expiry'] === true) {
204
                $soapParameters["NotAfter"] = $expiry->format('c');
205
            }
206
            $soapExpiryChange = $soap->setRequestParameters(
207
                    $soapReqnum, $soapParameters);
208
            if ($soapExpiryChange === false) {
209
                throw new Exception("Error when sending SOAP request (unable to change expiry date).");
210
            }
211
            // retrieve the raw request to prepare for signature and approval
212
            // this seems to come out base64-decoded already; maybe PHP
213
            // considers this "convenience"? But we need it as sent on
214
            // the wire, so re-encode it!
215
            $soapCleartext = $soap->getRawRequest($soapReqnum);
216
217
            $this->loggerInstance->debug(2, "Actual received SOAP response for getRawRequest was:\n\n");
218
            $this->loggerInstance->debug(2, $soap->__getLastResponse());
219
            // for obnoxious reasons, we have to dump the request into a file and let pkcs7_sign read from the file
220
            // rather than just using the string. Grr.
221
            $tempdir = \core\common\Entity::createTemporaryDirectory("test");
222
            file_put_contents($tempdir['dir'] . "/content.txt", $soapCleartext);
223
            // retrieve our RA cert from filesystem                    
224
            // the RA certificates are not needed right now because we
225
            // have resorted to S/MIME signatures with openssl command-line
226
            // rather than the built-in functions. But that may change in
227
            // the future, so let's park these two lines for future use.
228
            // $raCertFile = file_get_contents(ROOT . "/config/SilverbulletClientCerts/edupki-test-ra.pem");
229
            // $raCert = openssl_x509_read($raCertFile);
230
            // $raKey = openssl_pkey_get_private("file://" . ROOT . "/config/SilverbulletClientCerts/edupki-test-ra.clearkey");
231
            // sign the data, using cmdline because openssl_pkcs7_sign produces strange results
232
            // -binary didn't help, nor switch -md to sha1 sha256 or sha512
233
            $this->loggerInstance->debug(2, "Actual content to be signed is this:\n  $soapCleartext\n");
234
            $execCmd = \config\Master::PATHS['openssl'] . " smime -sign -binary -in " . $tempdir['dir'] . "/content.txt -out " . $tempdir['dir'] . "/signature.txt -outform pem -inkey " . $this->locationRaKey . " -signer " . $this->locationRaCert;
235
            $this->loggerInstance->debug(2, "Calling openssl smime with following cmdline:   $execCmd\n");
236
            $output = [];
237
            $return = 999;
238
            exec($execCmd, $output, $return);
239
            if ($return !== 0) {
240
                throw new Exception("Non-zero return value from openssl smime!");
241
            }
242
            // and get the signature blob back from the filesystem
243
            $detachedSig = trim(file_get_contents($tempdir['dir'] . "/signature.txt"));
244
            $this->loggerInstance->debug(5, "Request for server approveRequest has parameters:\n");
245
            $this->loggerInstance->debug(5, $soapReqnum . "\n");
246
            $this->loggerInstance->debug(5, $soapCleartext . "\n"); // PHP magically encodes this as base64 while sending!
247
            $this->loggerInstance->debug(5, $detachedSig . "\n");
248
            $soapIssueCert = $soap->approveRequest($soapReqnum, $soapCleartext, $detachedSig);
249
            $this->loggerInstance->debug(5, "approveRequest Request was: \n" . $soap->__getLastRequest());
250
            $this->loggerInstance->debug(5, "approveRequest Response was: \n" . $soap->__getLastResponse());
251
            if ($soapIssueCert === FALSE) {
252
                throw new Exception("The locally approved request was NOT processed by the CA.");
253
            }
254
        } catch (SoapFault $e) {
255
            throw new Exception("SoapFault: Error when sending or receiving SOAP message: " . "{$e->faultcode}: {$e->faultname}: {$e->faultstring}: {$e->faultactor}: {$e->detail}: {$e->headerfault}\n");
256
        } catch (Exception $e) {
257
            throw new Exception("Exception: Something odd happened between the SOAP requests:" . $e->getMessage());
258
        }
259
        return $soapReqnum;
260
    }
261
262
    /**
263
     * Polls the CA regularly until it gets the certificate for the request at hand. Gives up after 5 minutes.
264
     * 
265
     * @param int  $soapReqnum the certificate request for which the cert should be picked up
266
     * @param bool $wait       whether to wait until the cert is issued or return immediately
267
     * @return array|false the certificate along with some meta info, or false if we did not want to wait or got a timeout
268
     * @throws Exception
269
     */
270
    public function pickupFinalCert($soapReqnum, $wait)
271
    {
272
        try {
273
            $soap = $this->initEduPKISoapSession("RA");
274
            $counter = 0;
275
            $parsedCert = FALSE;
276
            $x509 = new common\X509();
277
            while ($parsedCert === FALSE && $counter < 300) {
278
                $soapCert = $soap->getCertificateByRequestSerial($soapReqnum);
279
280
                if (strlen($soapCert) > 10) { // we got the cert
281
                    $parsedCert = $x509->processCertificate($soapCert);
282
                } elseif ($wait) { // let's wait five seconds and try again
283
                    $counter += 5;
284
                    sleep(5);
285
                } else {
286
                    return FALSE; // don't wait, abort without result
287
                }
288
            }
289
            // we should now have an array
290
            if ($parsedCert === FALSE) {
291
                throw new Exception("We did not actually get a certificate after waiting for 5 minutes.");
292
            }
293
            // let's get the CA certificate chain
294
295
            $caInfo = $soap->getCAInfo();
296
            $certList = $x509->splitCertificate($caInfo->CAChain[0]);
297
            // find the root
298
            $theRoot = "";
299
            foreach ($certList as $oneCert) {
300
                $content = $x509->processCertificate($oneCert);
301
                if ($content['root'] == 1) {
302
                    $theRoot = $content;
303
                }
304
            }
305
            if ($theRoot == "") {
306
                throw new Exception("CAInfo has no root certificate for us!");
307
            }
308
        } catch (SoapFault $e) {
309
            throw new Exception("SoapFault: Error when sending or receiving SOAP message: " . "{$e->faultcode}: {$e->faultname}: {$e->faultstring}: {$e->faultactor}: {$e->detail}: {$e->headerfault}\n");
310
        } catch (Exception $e) {
311
            throw new Exception("Exception: Something odd happened between the SOAP requests:" . $e->getMessage());
312
        }
313
        return [
314
            "CERT" => openssl_x509_read($parsedCert['pem']),
315
            "SERIAL" => $parsedCert['full_details']['serialNumber'],
316
            "ISSUER" => $theRoot,
317
            "ROOT" => $theRoot,
318
        ];
319
    }
320
321
    /**
322
     * revokes a certificate
323
     * 
324
     * @param string $serial the serial, as a string because it is a 128 bit number
325
     * @return void
326
     * @throws Exception
327
     */
328
    public function revokeCertificate($serial): void
329
    {
330
        try {
331
            $soap = $this->initEduPKISoapSession("RA");
332
            $soapRevocationSerial = $soap->newRevocationRequest(["Serial", $serial], "");
333
            if ($soapRevocationSerial == 0) {
334
                throw new Exception("Unable to create revocation request, serial number was zero.");
335
            }
336
            // retrieve the raw request to prepare for signature and approval
337
            $soapRawRevRequest = $soap->getRawRevocationRequest($soapRevocationSerial);
338
            if (strlen($soapRawRevRequest) < 10) { // very basic error handling
339
                throw new Exception("Suspiciously short data to sign!");
340
            }
341
            // for obnoxious reasons, we have to dump the request into a file and let pkcs7_sign read from the file
342
            // rather than just using the string. Grr.
343
            $tempdir = \core\common\Entity::createTemporaryDirectory("test");
344
            file_put_contents($tempdir['dir'] . "/content.txt", $soapRawRevRequest);
345
            // retrieve our RA cert from filesystem
346
            // sign the data, using cmdline because openssl_pkcs7_sign produces strange results
347
            // -binary didn't help, nor switch -md to sha1 sha256 or sha512
348
            $this->loggerInstance->debug(5, "Actual content to be signed is this:\n$soapRawRevRequest\n");
349
        $execCmd = \config\Master::PATHS['openssl'] . " smime -sign -binary -in " . $tempdir['dir'] . "/content.txt -out " . $tempdir['dir'] . "/signature.txt -outform pem -inkey " . $this->locationRaKey . " -signer " . $this->locationRaCert;
350
            $this->loggerInstance->debug(2, "Calling openssl smime with following cmdline: $execCmd\n");
351
            $output = [];
352
            $return = 999;
353
            exec($execCmd, $output, $return);
354
            if ($return !== 0) {
355
                throw new Exception("Non-zero return value from openssl smime!");
356
            }
357
            // and get the signature blob back from the filesystem
358
            $detachedSig = trim(file_get_contents($tempdir['dir'] . "/signature.txt"));
359
            $soapIssueRev = $soap->approveRevocationRequest($soapRevocationSerial, $soapRawRevRequest, $detachedSig);
360
            if ($soapIssueRev === FALSE) {
361
                throw new Exception("The locally approved revocation request was NOT processed by the CA.");
362
            }
363
        } catch (Exception $e) {
364
            // PHP 7.1 can do this much better
365
            if (is_soap_fault($e)) {
366
                throw new Exception("Error when sending SOAP request: " . "{$e->faultcode}: {$e->faultstring}\n");
367
            }
368
            throw new Exception("Something odd happened while doing the SOAP request:" . $e->getMessage());
369
        }
370
    }
371
372
    /**
373
     * sets up a connection to the eduPKI SOAP interfaces
374
     * There is a public interface and an RA-restricted interface;
375
     * the latter needs an RA client certificate to identify the operator
376
     * 
377
     * @param string $type to which interface should we connect to - "PUBLIC" or "RA"
378
     * @return \SoapClient the connection object
379
     * @throws Exception
380
     */
381
    private function initEduPKISoapSession($type)
382
    {
383
        // set context parameters common to both endpoints
384
        $context_params = [
385
            'http' => [
386
                'timeout' => 60,
387
                'user_agent' => 'Stefan',
388
                'header'=> "Accept-language: en",
389
                'protocol_version' => 1.1
390
            ],
391
            
392
            'ssl' => [
393
                'verify_peer' => true,
394
                'verify_peer_name' => true,
395
                // below is the CA "/C=DE/O=Deutsche Telekom AG/OU=T-TeleSec Trust Center/CN=Deutsche Telekom Root CA 2"
396
                'cafile' => $this->locationWebRoot,
397
                'verify_depth' => 5,
398
                'capture_peer_cert' => true,
399
            ],
400
        ];
401
        $url = "";
402
        switch ($type) {
403
            case "PUBLIC":
404
                $url = $this->eduPkiEndpointPublic;
405
                $context_params['ssl']['peer_name'] = 'pki.edupki.org';
406
                break;
407
            case "RA":
408
                $url = $this->eduPkiEndpointRa;
409
                $context_params['ssl']['peer_name'] = 'ra.edupki.org';
410
                break;
411
            default:
412
                throw new Exception("Unknown type of eduPKI interface requested.");
413
        }
414
        if ($type == "RA") { // add client auth parameters to the context
415
            $context_params['ssl']['local_cert'] = $this->locationRaCert;
416
            $context_params['ssl']['local_pk'] = $this->locationRaKey;
417
            // $context_params['ssl']['passphrase'] = SilverbulletCertificate::EDUPKI_RA_PKEY_PASSPHRASE;
418
        }
419
        // initialise connection to eduPKI CA / eduroam RA
420
        $soap = new \SoapClient($url, [
421
            'soap_version' => SOAP_1_1,
422
            'trace' => TRUE,
423
            'exceptions' => TRUE,
424
            'connection_timeout' => 5, // if can't establish the connection within 5 sec, something's wrong
425
            'cache_wsdl' => WSDL_CACHE_NONE,
426
            'user_agent' => 'eduroam CAT to eduPKI SOAP Interface',
427
            'features' => SOAP_SINGLE_ELEMENT_ARRAYS,
428
            'stream_context' => stream_context_create($context_params),
429
            'typemap' => [
430
                [
431
                    'type_ns' => 'http://www.w3.org/2001/XMLSchema',
432
                    'type_name' => 'integer',
433
                    'from_xml' => 'core\CertificationAuthorityEduPkiServer::soapFromXmlInteger',
434
                    'to_xml' => 'core\CertificationAuthorityEduPkiServer::soapToXmlInteger',
435
                ],
436
            ],
437
                ]
438
        );
439
        return $soap;
440
    }
441
442
    /**
443
     * a function that converts integers beyond PHP_INT_MAX to strings for
444
     * sending in XML messages
445
     *
446
     * taken and adapted from 
447
     * https://www.uni-muenster.de/WWUCA/de/howto-special-phpsoap.html
448
     * 
449
     * @param string $x the integer as an XML fragment
450
     * @return array the integer in array notation
451
     */
452
    public function soapFromXmlInteger($x)
453
    {
454
        $y = simplexml_load_string($x);
455
        return array(
456
            $y->getName(),
457
            $y->__toString()
458
        );
459
    }
460
461
    /**
462
     * a function that converts integers beyond PHP_INT_MAX to strings for
463
     * sending in XML messages
464
     * 
465
     * @param array $x the integer in array notation
466
     * @return string the integer as string in an XML fragment
467
     */
468
    public function soapToXmlInteger($x)
469
    {
470
        return '<' . $x[0] . '>'
471
                . htmlentities($x[1], ENT_NOQUOTES | ENT_XML1)
472
                . '</' . $x[0] . '>';
473
    }
474
475
    /**
476
     * generates a CSR which eduPKI likes (DC components etc.)
477
     * 
478
     * @param \OpenSSLAsymmetricKey $privateKey a private key
479
     * @param string                $fed        name of the federation, for C= field
480
     * @param string                $username   username, for CN= field
481
     * @return array the CSR along with some meta information
482
     * @throws Exception
483
     */
484
    public function generateCompatibleCsr($privateKey, $fed, $username): array
485
    {
486
        $tempdirArray = \core\common\Entity::createTemporaryDirectory("test");
487
        $tempdir = $tempdirArray['dir'];
488
        // dump private key into directory
489
        $outstring = "";
490
        openssl_pkey_export($privateKey, $outstring);
491
        file_put_contents($tempdir . "/pkey.pem", $outstring);
492
        // PHP can only do one DC in the Subject. But we need three.
493
        $execCmd = \config\Master::PATHS['openssl'] . " req -new -sha256 -key $tempdir/pkey.pem -out $tempdir/request.csr -subj /DC=test/DC=test/DC=eduroam/C=$fed/O=" . \config\ConfAssistant::CONSORTIUM['name'] . "/OU=$fed/CN=$username/emailAddress=$username";
494
        $this->loggerInstance->debug(2, "Calling openssl req with following cmdline: $execCmd\n");
495
        $output = [];
496
        $return = 999;
497
        exec($execCmd, $output, $return);
498
        if ($return !== 0) {
499
            throw new Exception("Non-zero return value from openssl req!");
500
        }
501
        $newCsr = file_get_contents("$tempdir/request.csr");
502
        // remove the temp dir!
503
        unlink("$tempdir/pkey.pem");
504
        unlink("$tempdir/request.csr");
505
        rmdir($tempdir);
506
        if ($newCsr === FALSE) {
507
            throw new Exception("Unable to create a CSR!");
508
        }
509
        return [
510
            "CSR_STRING" => $newCsr, // a string
511
            "CSR_OBJECT" => NULL,
512
            "USERNAME" => $username,
513
            "FED" => $fed
514
        ];
515
    }
516
517
    /**
518
     * generates a private key eduPKI can handle
519
     * 
520
     * @return \OpenSSLAsymmetricKey the key
521
     * @throws Exception
522
     */
523
    public function generateCompatiblePrivateKey()
524
    {
525
        $key = openssl_pkey_new(['private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA, 'encrypt_key' => FALSE]);
526
        if ($key === FALSE || is_resource($key)) {
527
            throw new Exception("Unable to generate a private key / not a PHP8 object.");
528
        }
529
        return $key;
530
    }
531
532
    /**
533
     * CAs don't have any local caching or other freshness issues
534
     * 
535
     * @return void
536
     */
537
    public function updateFreshness()
538
    {
539
        // nothing to be done here.
540
    }
541
}
542