commiting uncommited changes on live site
[weblabels.fsf.org.git] / crm.fsf.org / 20131203 / files / sites / all / modules-new / civicrm / vendor / civicrm / civicrm-cxn-rpc / src / DefaultCertificateValidator.php
1 <?php
2
3 /*
4 * This file is part of the civicrm-cxn-rpc package.
5 *
6 * Copyright (c) CiviCRM LLC <info@civicrm.org>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this package.
10 */
11
12 namespace Civi\Cxn\Rpc;
13
14 use Civi\Cxn\Rpc\Exception\ExpiredCertException;
15 use Civi\Cxn\Rpc\Exception\InvalidCertException;
16 use Civi\Cxn\Rpc\Http\HttpInterface;
17 use Civi\Cxn\Rpc\Http\PhpHttp;
18
19 /**
20 * Class DefaultCertificateValidator
21 * @package Civi\Cxn\Rpc
22 *
23 * The default certificate validator will:
24 * - Check that the certificate is signed by canonical CA.
25 * - Check that the certificate has not been revoked by the canonical CA
26 * (using the CRL URL of the CA).
27 *
28 * Validating the CRL requires issuing HTTP requests. To improve performance,
29 * consider replacing the default $http instance (PhpHttp) with something
30 * that supports caching.
31 */
32 class DefaultCertificateValidator implements CertificateValidatorInterface {
33
34 /**
35 * Specify that content should be auto-loaded via HTTP.
36 */
37 const AUTOLOAD = '*auto*';
38
39 /**
40 * @var string
41 * The CA certificate (PEM-encoded).
42 * Use DefaultCertificateValidator::AUTOLOAD to use the bundled CiviRootCA.
43 */
44 protected $caCert;
45
46 /**
47 * @var string
48 * The URL for downloading the CRL.
49 * Use DefaultCertificateValidator::AUTOLOAD to extract from $caCert.
50 */
51 protected $crlUrl = DefaultCertificateValidator::AUTOLOAD;
52
53 /**
54 * @var string
55 * The CRL data.
56 * Use DefaultCertificateValidator::AUTOLOAD to download via HTTP.
57 */
58 protected $crl;
59
60 /**
61 * @var string
62 * The certificate which signs CRLs (PEM-encoded).
63 * Use DefaultCertificateValidator::AUTOLOAD to download via HTTP.
64 */
65 protected $crlDistCert;
66
67 /**
68 * @var HttpInterface|string
69 * The service to use when autoloading data.
70 * Use DefaultCertificateValidator::AUTOLOAD to download via HTTP.
71 */
72 protected $http;
73
74 /**
75 * @param string $caCertPem
76 * @param string $crlDistCertPem
77 * @param string $crlPem
78 * @param HttpInterface|string $http
79 */
80 public function __construct(
81 $caCertPem = DefaultCertificateValidator::AUTOLOAD,
82 $crlDistCertPem = DefaultCertificateValidator::AUTOLOAD,
83 $crlPem = DefaultCertificateValidator::AUTOLOAD,
84 $http = DefaultCertificateValidator::AUTOLOAD) {
85
86 $this->caCert = $caCertPem;
87 $this->crlDistCert = $crlDistCertPem;
88 $this->crl = $crlPem;
89 $this->http = $http;
90 }
91
92 /**
93 * Determine whether an X.509 certificate is currently valid.
94 *
95 * @param string $certPem
96 * PEM-encoded certificate.
97 * @throws InvalidCertException
98 * Invalid certificates are reported as exceptions.
99 */
100 public function validateCert($certPem) {
101 if ($this->getCaCert()) {
102 self::validate($certPem, $this->getCaCert(), $this->getCrl(), $this->getCrlDistCert());
103 }
104 }
105
106 protected static function validate($certPem, $caCertPem, $crlPem = NULL, $crlDistCertPem = NULL) {
107 $caCertObj = X509Util::loadCACert($caCertPem);
108
109 $certObj = new \File_X509();
110 $certObj->loadCA($caCertPem);
111
112 if ($crlPem !== NULL) {
113 $crlObj = new \File_X509();
114 if ($crlDistCertPem) {
115 $crlDistCertObj = X509Util::loadCrlDistCert($crlDistCertPem, NULL, $caCertPem);
116 if ($crlDistCertObj->getSubjectDN(FILE_X509_DN_STRING) !== $caCertObj->getSubjectDN(FILE_X509_DN_STRING)) {
117 throw new InvalidCertException(sprintf("CRL distributor (%s) does not act on behalf of this CA (%s)",
118 $crlDistCertObj->getSubjectDN(FILE_X509_DN_STRING),
119 $caCertObj->getSubjectDN(FILE_X509_DN_STRING)
120 ));
121 }
122 try {
123 self::validate($crlDistCertPem, $caCertPem);
124 }
125 catch (InvalidCertException $ie) {
126 throw new InvalidCertException("CRL distributor has an invalid certificate", 0, $ie);
127 }
128 $crlObj->loadCA($crlDistCertPem);
129 }
130 $crlObj->loadCA($caCertPem);
131 $crlObj->loadCRL($crlPem);
132 if (!$crlObj->validateSignature()) {
133 throw new InvalidCertException("CRL signature is invalid");
134 }
135 }
136
137 $parsedCert = $certObj->loadX509($certPem);
138 if ($crlPem !== NULL) {
139 if (empty($parsedCert)) {
140 throw new InvalidCertException("Identity is invalid. Empty certificate.");
141 }
142 if (empty($parsedCert['tbsCertificate']['serialNumber'])) {
143 throw new InvalidCertException("Identity is invalid. No serial number.");
144 }
145 $revoked = $crlObj->getRevoked($parsedCert['tbsCertificate']['serialNumber']->toString());
146 if (!empty($revoked)) {
147 throw new InvalidCertException("Identity is invalid. Certificate revoked.");
148 }
149 }
150
151 if (!$certObj->validateSignature()) {
152 throw new InvalidCertException("Identity is invalid. Certificate is not signed by proper CA.");
153 }
154 if (!$certObj->validateDate(Time::getTime())) {
155 throw new ExpiredCertException("Identity is invalid. Certificate expired.");
156 }
157 }
158
159 /**
160 * @return string
161 */
162 public function getCaCert() {
163 if ($this->caCert === self::AUTOLOAD) {
164 $this->caCert = file_get_contents(Constants::getCert());
165 }
166 return $this->caCert;
167 }
168
169 /**
170 * @param string $caCert
171 * @return $this
172 */
173 public function setCaCert($caCert) {
174 $this->caCert = $caCert;
175 return $this;
176 }
177
178 /**
179 * Determine the CRL URL which corresponds to this CA.
180 */
181 public function getCrlUrl() {
182 if ($this->crlUrl === self::AUTOLOAD) {
183 $this->crlUrl = NULL; // Default if we can't find something else.
184 $caCertObj = X509Util::loadCACert($this->getCaCert());
185 // There can be multiple DPs, but in practice CiviRootCA only has one.
186 $crlDPs = $caCertObj->getExtension('id-ce-cRLDistributionPoints');
187 if (is_array($crlDPs)) {
188 foreach ($crlDPs as $crlDP) {
189 foreach ($crlDP['distributionPoint']['fullName'] as $fullName) {
190 if (isset($fullName['uniformResourceIdentifier'])) {
191 $this->crlUrl = $fullName['uniformResourceIdentifier'];
192 break 2;
193 }
194 }
195 }
196 }
197 }
198 return $this->crlUrl;
199 }
200
201 /**
202 * @param string $crlUrl
203 * @return $this
204 */
205 public function setCrlUrl($crlUrl) {
206 $this->crlUrl = $crlUrl;
207 return $this;
208 }
209
210 /**
211 * @return string
212 */
213 public function getCrlDistCert() {
214 if ($this->crlDistCert === self::AUTOLOAD) {
215 if ($this->getCrlUrl()) {
216 $url = preg_replace('/\.crl/', '/dist.crt', $this->getCrlUrl());
217 list ($headers, $blob, $code) = $this->getHttp()->send('GET', $url, '');
218 if ($code != 200) {
219 throw new \RuntimeException("Certificate validation failed. Cannot load CRL distribution certificate: $url");
220 }
221 $this->crlDistCert = $blob;
222 }
223 else {
224 $this->crlDistCert = NULL;
225 }
226 }
227 return $this->crlDistCert;
228 }
229
230 /**
231 * @param string $crlDistCert
232 * @return $this
233 */
234 public function setCrlDistCert($crlDistCert) {
235 $this->crlDistCert = $crlDistCert;
236 return $this;
237 }
238
239 /**
240 * @return string
241 */
242 public function getCrl() {
243 if ($this->crl === self::AUTOLOAD) {
244 $url = $this->getCrlUrl();
245 if ($url) {
246 list ($headers, $blob, $code) = $this->getHttp()->send('GET', $url, '');
247 if ($code != 200) {
248 throw new \RuntimeException("Certificate validation failed. Cannot load CRL: $url");
249 }
250 $this->crl = $blob;
251 }
252 else {
253 $this->crl = NULL;
254 }
255 }
256 return $this->crl;
257 }
258
259 /**
260 * @param string $crl
261 * @return $this
262 */
263 public function setCrl($crl) {
264 $this->crl = $crl;
265 return $this;
266 }
267
268 /**
269 * @return HttpInterface
270 */
271 public function getHttp() {
272 if ($this->http === self::AUTOLOAD) {
273 $this->http = new PhpHttp();
274 }
275 return $this->http;
276 }
277
278 /**
279 * @param HttpInterface $http
280 * @return $this
281 */
282 public function setHttp($http) {
283 $this->http = $http;
284 return $this;
285 }
286
287 }