Passed
Push — release_2_0 ( cf22ac...b0f4f3 )
by Stefan
07:48
created

triggerNewOCSPStatement()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 2
c 2
b 0
f 0
dl 0
loc 4
rs 10
cc 1
nc 1
nop 1
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;
15
use \SoapFault;
16
17
class CertificationAuthorityEduPkiServer extends EntityWithDBProperties implements CertificationAuthorityInterface
18
{
19
20
    private const LOCATION_RA_CERT = ROOT . "/config/SilverbulletClientCerts/edupki-test-ra.pem";
21
    private const LOCATION_RA_KEY = ROOT . "/config/SilverbulletClientCerts/edupki-test-ra.clearkey";
22
    private const LOCATION_WEBROOT = ROOT . "/config/SilverbulletClientCerts/eduPKI-webserver-root.pem";
23
    private const EDUPKI_RA_ID = 700;
24
    private const EDUPKI_CERT_PROFILE = "Radius Server SOAP";
25
    private const EDUPKI_RA_PKEY_PASSPHRASE = "...";
26
27
    /**
28
     * sets up the environment so that we can talk to eduPKI
29
     * 
30
     * @throws Exception
31
     */
32
    public function __construct()
33
    {
34
        $this->databaseType = "INST";
35
        parent::__construct();
36
37
        if (stat(CertificationAuthorityEduPkiServer::LOCATION_RA_CERT) === FALSE) {
38
            throw new Exception("RA operator PEM file not found: " . CertificationAuthorityEduPkiServer::LOCATION_RA_CERT);
39
        }
40
        if (stat(CertificationAuthorityEduPkiServer::LOCATION_RA_KEY) === FALSE) {
41
            throw new Exception("RA operator private key file not found: " . CertificationAuthorityEduPkiServer::LOCATION_RA_KEY);
42
        }
43
        if (stat(CertificationAuthorityEduPkiServer::LOCATION_WEBROOT) === FALSE) {
44
            throw new Exception("CA website root CA file not found: " . CertificationAuthorityEduPkiServer::LOCATION_WEBROOT);
45
        }
46
    }
47
48
    /**
49
     * Creates an updated OCSP statement. Nothing to be done here - eduPKI have
50
     * their own OCSP responder and the certs point to it. So we are not in the 
51
     * loop.
52
     * 
53
     * @param string $serial serial number of the certificate. Serials are 128 bit, so forcibly a string.
54
     * @return string a dummy string instead of a real statement
55
     */
56
    public function triggerNewOCSPStatement($serial): string
57
    {
58
        unset($serial); // not needed
59
        return "EXTERNAL";
60
    }
61
62
    /**
63
     * signs a CSR and returns the certificate (blocking wait)
64
     * 
65
     * @param array   $csr        the request structure. The member $csr['CSR'] must contain the CSR in *PEM* format
66
     * @param integer $expiryDays how many days should the certificate be valid
67
     * @return array the certificate with some meta info
68
     * @throws Exception
69
     */
70
    public function signRequest($csr, $expiryDays): array {
71
        $revocationPin = common\Entity::randomString(10, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
72
        $soapReqnum = $this->sendRequestToCa($csr, $revocationPin, $expiryDays);
73
        sleep(55);
74
        // now, get the actual cert from the CA
75
        return $this->pickupFinalCert($soapReqnum, TRUE);
76
    }
77
    
78
    /**
79
     * sends the request to the CA and asks for the certificate. Does not block
80
     * until the certificate is issued, it needs to be picked up seperately
81
     * using its request number.
82
     * 
83
     * @param array  $csr           the CSR to sign. The member $csr['CSR'] must contain the CSR in *PEM* format
84
     * @param string $revocationPin a PIN to be able to revoke the cert later on
85
     * @param int    $expiryDays    how many days should the certificate be valid
86
     * @return int the request serial number
87
     * @throws Exception
88
     */
89
    public function sendRequestToCa($csr, $revocationPin, $expiryDays): int
0 ignored issues
show
Unused Code introduced by
The method parameter $expiryDays is never used
Loading history...
Unused Code introduced by
The parameter $expiryDays is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

89
    public function sendRequestToCa($csr, $revocationPin, /** @scrutinizer ignore-unused */ $expiryDays): int

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

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