Passed
Push — release_2_0 ( 42847d...5cfef1 )
by Stefan
14:05
created

generateCompatiblePrivateKey()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
c 1
b 0
f 0
dl 0
loc 6
rs 10
cc 2
nc 2
nop 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
namespace core;
13
14
use \Exception;
15
use \SoapFault;
16
17
class CertificationAuthorityEduPkiServer extends EntityWithDBProperties implements CertificationAuthorityInterface {
18
19
    private const LOCATION_RA_CERT = ROOT . "/config/SilverbulletClientCerts/edupki-test-ra.pem";
20
    private const LOCATION_RA_KEY = ROOT . "/config/SilverbulletClientCerts/edupki-test-ra.clearkey";
21
    private const LOCATION_WEBROOT = ROOT . "/config/SilverbulletClientCerts/eduPKI-webserver-root.pem";
22
    private const EDUPKI_RA_ID = 700;
23
    private const EDUPKI_CERT_PROFILE = "Radius Server SOAP";
24
    private const EDUPKI_RA_PKEY_PASSPHRASE = "...";
25
26
    /**
27
     * sets up the environment so that we can talk to eduPKI
28
     * 
29
     * @throws Exception
30
     */
31
    public function __construct() {
32
        $this->databaseType = "INST";
33
        parent::__construct();
34
35
        if (stat(CertificationAuthorityEduPkiServer::LOCATION_RA_CERT) === FALSE) {
36
            throw new Exception("RA operator PEM file not found: " . CertificationAuthorityEduPkiServer::LOCATION_RA_CERT);
37
        }
38
        if (stat(CertificationAuthorityEduPkiServer::LOCATION_RA_KEY) === FALSE) {
39
            throw new Exception("RA operator private key file not found: " . CertificationAuthorityEduPkiServer::LOCATION_RA_KEY);
40
        }
41
        if (stat(CertificationAuthorityEduPkiServer::LOCATION_WEBROOT) === FALSE) {
42
            throw new Exception("CA website root CA file not found: " . CertificationAuthorityEduPkiServer::LOCATION_WEBROOT);
43
        }
44
    }
45
46
    /**
47
     * Creates an updated OCSP statement. Nothing to be done here - eduPKI have
48
     * their own OCSP responder and the certs point to it. So we are not in the 
49
     * loop.
50
     * 
51
     * @param string $serial serial number of the certificate. Serials are 128 bit, so forcibly a string.
52
     * @return string a dummy string instead of a real statement
53
     */
54
    public function triggerNewOCSPStatement($serial): string {
55
        unset($serial); // not needed
56
        return "EXTERNAL";
57
    }
58
59
    /**
60
     * signs a CSR
61
     * 
62
     * @param array   $csr        the request structure. The member $csr['CSR'] must contain the CSR in *PEM* format
63
     * @param integer $expiryDays how many days should the certificate be valid
64
     * @return array the certificate with some meta info
65
     * @throws Exception
66
     */
67
    public function signRequest($csr, $expiryDays): array {
0 ignored issues
show
Unused Code introduced by
The method parameter $expiryDays is never used
Loading history...
68
        // initialise connection to eduPKI CA / eduroam RA and send the request to them
69
        try {
70
            $altArray = [# Array mit den Subject Alternative Names
71
                 "email:" . $csr["USERMAIL"]
72
            ];
73
            $soapPub = $this->initEduPKISoapSession("PUBLIC");
74
            $this->loggerInstance->debug(5, "FIRST ACTUAL SOAP REQUEST (Public, newRequest)!\n");
75
            $this->loggerInstance->debug(5, "PARAM_1: " . CertificationAuthorityEduPkiServer::EDUPKI_RA_ID . "\n");
76
            $this->loggerInstance->debug(5, "PARAM_2: " . $csr["CSR"] . "\n");
77
            $this->loggerInstance->debug(5, "PARAM_3: ");
78
            $this->loggerInstance->debug(5, $altArray);
79
            $this->loggerInstance->debug(5, "PARAM_4: " . CertificationAuthorityEduPkiServer::EDUPKI_CERT_PROFILE . "\n");
80
            $this->loggerInstance->debug(5, "PARAM_5: " . sha1("notused") . "\n");
81
            $this->loggerInstance->debug(5, "PARAM_6: " . $csr["USERNAME"] . "\n");
82
            $this->loggerInstance->debug(5, "PARAM_7: " . $csr["USERMAIL"] . "\n");
83
            $this->loggerInstance->debug(5, "PARAM_8: " . ProfileSilverbullet::PRODUCTNAME . "\n");
84
            $this->loggerInstance->debug(5, "PARAM_9: false\n");
85
            $soapNewRequest = $soapPub->newRequest(
86
                    CertificationAuthorityEduPkiServer::EDUPKI_RA_ID, # RA-ID
87
                    $csr["CSR"], # Request im PEM-Format
88
                    $altArray, # altNames
89
                    CertificationAuthorityEduPkiServer::EDUPKI_CERT_PROFILE, # Zertifikatprofil
90
                    sha1("notused"), # PIN
91
                    $csr["USERNAME"], # Name des Antragstellers
92
                    $csr["USERMAIL"], # Kontakt-E-Mail
93
                    ProfileSilverbullet::PRODUCTNAME, # Organisationseinheit des Antragstellers
94
                    false                   # Veröffentlichen des Zertifikats?
95
            );
96
            $this->loggerInstance->debug(5, $soapPub->__getLastRequest());
97
            $this->loggerInstance->debug(5, $soapPub->__getLastResponse());
98
            if ($soapNewRequest == 0) {
99
                throw new Exception("Error when sending SOAP request (request serial number was zero). No further details available.");
100
            }
101
            $soapReqnum = intval($soapNewRequest);
102
        } catch (Exception $e) {
103
            // PHP 7.1 can do this much better
104
            if (is_soap_fault($e)) {
105
                throw new Exception("Error when sending SOAP request: " . "{$e->faultcode}:  {
106
                    $e->faultstring
107
                }\n");
108
            }
109
            throw new Exception("Something odd happened while doing the SOAP request:" . $e->getMessage());
110
        }
111
        try {
112
            $soap = $this->initEduPKISoapSession("RA");
113
            // tell the CA the desired expiry date of the new certificate
114
            $expiry = new \DateTime();
115
            // FIXME the current test interface does not like 5 years...
116
            //$expiry->modify("+$expiryDays day");
117
            $expiry->modify("+365 day");
118
            $expiry->setTimezone(new \DateTimeZone("UTC"));
119
            echo $csr['SUBJECT'];
0 ignored issues
show
Security Cross-Site Scripting introduced by
$csr['SUBJECT'] can contain request data and is used in output context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. Read from $_POST, and array('CSR' => $_POST['CSR'], 'CN' => '[email protected]', 'USERNAME' => 'Someone', 'USERMAIL' => '[email protected]', 'SUBJECT' => implode(',', $DN), 'FED' => $country) is assigned to $newCsrWithMeta
    in web/admin/action_req_certificate.php on line 125
  2. CertificationAuthorityEduPkiServer::signRequest() is called
    in web/admin/action_req_certificate.php on line 128
  3. Enters via parameter $csr
    in core/CertificationAuthorityEduPkiServer.php on line 67

Preventing Cross-Site-Scripting Attacks

Cross-Site-Scripting allows an attacker to inject malicious code into your website - in particular Javascript code, and have that code executed with the privileges of a visiting user. This can be used to obtain data, or perform actions on behalf of that visiting user.

In order to prevent this, make sure to escape all user-provided data:

// for HTML
$sanitized = htmlentities($tainted, ENT_QUOTES);

// for URLs
$sanitized = urlencode($tainted);

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

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