Test Failed
Push — master ( 83b526...bd639c )
by Stefan
23:38
created

sendRequestToCa()   F

Complexity

Conditions 14
Paths 691

Size

Total Lines 125
Code Lines 88

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 88
c 2
b 0
f 0
dl 0
loc 125
rs 2.2851
cc 14
nc 691
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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