Project

General

Profile

Download (54.9 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * certs.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2008-2013 BSD Perimeter
7
 * Copyright (c) 2013-2016 Electric Sheep Fencing
8
 * Copyright (c) 2014-2019 Rubicon Communications, LLC (Netgate)
9
 * Copyright (c) 2008 Shrew Soft Inc. All rights reserved.
10
 * All rights reserved.
11
 *
12
 * Licensed under the Apache License, Version 2.0 (the "License");
13
 * you may not use this file except in compliance with the License.
14
 * You may obtain a copy of the License at
15
 *
16
 * http://www.apache.org/licenses/LICENSE-2.0
17
 *
18
 * Unless required by applicable law or agreed to in writing, software
19
 * distributed under the License is distributed on an "AS IS" BASIS,
20
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
 * See the License for the specific language governing permissions and
22
 * limitations under the License.
23
 */
24

    
25
define("OPEN_SSL_CONF_PATH", "/etc/ssl/openssl.cnf");
26

    
27
require_once("functions.inc");
28

    
29
global $openssl_digest_algs;
30
$openssl_digest_algs = array("sha1", "sha224", "sha256", "sha384", "sha512");
31

    
32
global $openssl_crl_status;
33
/* Numbers are set in the RFC: https://www.ietf.org/rfc/rfc5280.txt */
34
$openssl_crl_status = array(
35
	-1 => "No Status (default)",
36
	0  => "Unspecified",
37
	1 => "Key Compromise",
38
	2 => "CA Compromise",
39
	3 => "Affiliation Changed",
40
	4 => "Superseded",
41
	5 => "Cessation of Operation",
42
	6 => "Certificate Hold",
43
	9 => 'Privilege Withdrawn',
44
);
45

    
46
global $cert_altname_types;
47
$cert_altname_types = array(
48
	'DNS' => gettext('FQDN or Hostname'),
49
	'IP' => gettext('IP address'),
50
	'URI' => gettext('URI'),
51
	'email' => gettext('email address'),
52
);
53

    
54

    
55
function & lookup_ca($refid) {
56
	global $config;
57

    
58
	if (is_array($config['ca'])) {
59
		foreach ($config['ca'] as & $ca) {
60
			if ($ca['refid'] == $refid) {
61
				return $ca;
62
			}
63
		}
64
	}
65

    
66
	return false;
67
}
68

    
69
function & lookup_ca_by_subject($subject) {
70
	global $config;
71

    
72
	if (is_array($config['ca'])) {
73
		foreach ($config['ca'] as & $ca) {
74
			$ca_subject = cert_get_subject($ca['crt']);
75
			if ($ca_subject == $subject) {
76
				return $ca;
77
			}
78
		}
79
	}
80

    
81
	return false;
82
}
83

    
84
function & lookup_cert($refid) {
85
	global $config;
86

    
87
	if (is_array($config['cert'])) {
88
		foreach ($config['cert'] as & $cert) {
89
			if ($cert['refid'] == $refid) {
90
				return $cert;
91
			}
92
		}
93
	}
94

    
95
	return false;
96
}
97

    
98
function & lookup_cert_by_name($name) {
99
	global $config;
100
	if (is_array($config['cert'])) {
101
		foreach ($config['cert'] as & $cert) {
102
			if ($cert['descr'] == $name) {
103
				return $cert;
104
			}
105
		}
106
	}
107
}
108

    
109
function & lookup_crl($refid) {
110
	global $config;
111

    
112
	if (is_array($config['crl'])) {
113
		foreach ($config['crl'] as & $crl) {
114
			if ($crl['refid'] == $refid) {
115
				return $crl;
116
			}
117
		}
118
	}
119

    
120
	return false;
121
}
122

    
123
function ca_chain_array(& $cert) {
124
	if ($cert['caref']) {
125
		$chain = array();
126
		$crt = lookup_ca($cert['caref']);
127
		$chain[] = $crt;
128
		while ($crt) {
129
			$caref = $crt['caref'];
130
			if ($caref) {
131
				$crt = lookup_ca($caref);
132
			} else {
133
				$crt = false;
134
			}
135
			if ($crt) {
136
				$chain[] = $crt;
137
			}
138
		}
139
		return $chain;
140
	}
141
	return false;
142
}
143

    
144
function ca_chain(& $cert) {
145
	if ($cert['caref']) {
146
		$ca = "";
147
		$cas = ca_chain_array($cert);
148
		if (is_array($cas)) {
149
			foreach ($cas as & $ca_cert) {
150
				$ca .= base64_decode($ca_cert['crt']);
151
				$ca .= "\n";
152
			}
153
		}
154
		return $ca;
155
	}
156
	return "";
157
}
158

    
159
function ca_import(& $ca, $str, $key = "", $serial = "") {
160
	global $config;
161

    
162
	$ca['crt'] = base64_encode($str);
163
	if (!empty($key)) {
164
		$ca['prv'] = base64_encode($key);
165
	}
166
	if (empty($serial)) {
167
		$ca['serial'] = 0;
168
	} else {
169
		$ca['serial'] = $serial;
170
	}
171
	$subject = cert_get_subject($str, false);
172
	$issuer = cert_get_issuer($str, false);
173

    
174
	// Find my issuer unless self-signed
175
	if ($issuer <> $subject) {
176
		$issuer_crt =& lookup_ca_by_subject($issuer);
177
		if ($issuer_crt) {
178
			$ca['caref'] = $issuer_crt['refid'];
179
		}
180
	}
181

    
182
	/* Correct if child certificate was loaded first */
183
	if (is_array($config['ca'])) {
184
		foreach ($config['ca'] as & $oca) {
185
			$issuer = cert_get_issuer($oca['crt']);
186
			if ($ca['refid'] <> $oca['refid'] && $issuer == $subject) {
187
				$oca['caref'] = $ca['refid'];
188
			}
189
		}
190
	}
191
	if (is_array($config['cert'])) {
192
		foreach ($config['cert'] as & $cert) {
193
			$issuer = cert_get_issuer($cert['crt']);
194
			if ($issuer == $subject) {
195
				$cert['caref'] = $ca['refid'];
196
			}
197
		}
198
	}
199
	return true;
200
}
201

    
202
function ca_create(& $ca, $keylen, $lifetime, $dn, $digest_alg = "sha256", $keytype = "RSA", $ecname = "brainpoolP256r1") {
203

    
204
	$args = array(
205
		"x509_extensions" => "v3_ca",
206
		"digest_alg" => $digest_alg,
207
		"encrypt_key" => false);
208
	if ($keytype == 'ECDSA') {
209
		$args["curve_name"] = $ecname;
210
		$args["private_key_type"] = OPENSSL_KEYTYPE_EC;
211
	} else {
212
		$args["private_key_bits"] = (int)$keylen;
213
		$args["private_key_type"] = OPENSSL_KEYTYPE_RSA;
214
	}
215

    
216
	// generate a new key pair
217
	$res_key = openssl_pkey_new($args);
218
	if (!$res_key) {
219
		return false;
220
	}
221

    
222
	// generate a certificate signing request
223
	$res_csr = openssl_csr_new($dn, $res_key, $args);
224
	if (!$res_csr) {
225
		return false;
226
	}
227

    
228
	// self sign the certificate
229
	$res_crt = openssl_csr_sign($res_csr, null, $res_key, $lifetime, $args);
230
	if (!$res_crt) {
231
		return false;
232
	}
233

    
234
	// export our certificate data
235
	if (!openssl_pkey_export($res_key, $str_key) ||
236
	    !openssl_x509_export($res_crt, $str_crt)) {
237
		return false;
238
	}
239

    
240
	// return our ca information
241
	$ca['crt'] = base64_encode($str_crt);
242
	$ca['prv'] = base64_encode($str_key);
243
	$ca['serial'] = 0;
244

    
245
	return true;
246
}
247

    
248
function ca_inter_create(& $ca, $keylen, $lifetime, $dn, $caref, $digest_alg = "sha256", $keytype = "RSA", $ecname = "brainpoolP256r1") {
249
	// Create Intermediate Certificate Authority
250
	$signing_ca =& lookup_ca($caref);
251
	if (!$signing_ca) {
252
		return false;
253
	}
254

    
255
	$signing_ca_res_crt = openssl_x509_read(base64_decode($signing_ca['crt']));
256
	$signing_ca_res_key = openssl_pkey_get_private(array(0 => base64_decode($signing_ca['prv']) , 1 => ""));
257
	if (!$signing_ca_res_crt || !$signing_ca_res_key) {
258
		return false;
259
	}
260
	$signing_ca_serial = ++$signing_ca['serial'];
261

    
262
	$args = array(
263
		"x509_extensions" => "v3_ca",
264
		"digest_alg" => $digest_alg,
265
		"encrypt_key" => false);
266
	if ($keytype == 'ECDSA') {
267
		$args["curve_name"] = $ecname;
268
		$args["private_key_type"] = OPENSSL_KEYTYPE_EC;
269
	} else {
270
		$args["private_key_bits"] = (int)$keylen;
271
		$args["private_key_type"] = OPENSSL_KEYTYPE_RSA;
272
	}
273

    
274
	// generate a new key pair
275
	$res_key = openssl_pkey_new($args);
276
	if (!$res_key) {
277
		return false;
278
	}
279

    
280
	// generate a certificate signing request
281
	$res_csr = openssl_csr_new($dn, $res_key, $args);
282
	if (!$res_csr) {
283
		return false;
284
	}
285

    
286
	// Sign the certificate
287
	$res_crt = openssl_csr_sign($res_csr, $signing_ca_res_crt, $signing_ca_res_key, $lifetime, $args, $signing_ca_serial);
288
	if (!$res_crt) {
289
		return false;
290
	}
291

    
292
	// export our certificate data
293
	if (!openssl_pkey_export($res_key, $str_key) ||
294
	    !openssl_x509_export($res_crt, $str_crt)) {
295
		return false;
296
	}
297

    
298
	// return our ca information
299
	$ca['crt'] = base64_encode($str_crt);
300
	$ca['prv'] = base64_encode($str_key);
301
	$ca['serial'] = 0;
302
	$ca['caref'] = $caref;
303

    
304
	return true;
305
}
306

    
307
function cert_import(& $cert, $crt_str, $key_str) {
308

    
309
	$cert['crt'] = base64_encode($crt_str);
310
	$cert['prv'] = base64_encode($key_str);
311

    
312
	$subject = cert_get_subject($crt_str, false);
313
	$issuer = cert_get_issuer($crt_str, false);
314

    
315
	// Find my issuer unless self-signed
316
	if ($issuer <> $subject) {
317
		$issuer_crt =& lookup_ca_by_subject($issuer);
318
		if ($issuer_crt) {
319
			$cert['caref'] = $issuer_crt['refid'];
320
		}
321
	}
322
	return true;
323
}
324

    
325
function cert_create(& $cert, $caref, $keylen, $lifetime, $dn, $type = "user", $digest_alg = "sha256", $keytype = "RSA", $ecname = "brainpoolP256r1") {
326

    
327
	$cert['type'] = $type;
328

    
329
	if ($type != "self-signed") {
330
		$cert['caref'] = $caref;
331
		$ca =& lookup_ca($caref);
332
		if (!$ca) {
333
			return false;
334
		}
335

    
336
		$ca_str_crt = base64_decode($ca['crt']);
337
		$ca_str_key = base64_decode($ca['prv']);
338
		$ca_res_crt = openssl_x509_read($ca_str_crt);
339
		$ca_res_key = openssl_pkey_get_private(array(0 => $ca_str_key, 1 => ""));
340
		if (!$ca_res_key) {
341
			return false;
342
		}
343
		if (empty($ca['serial'])) {
344
			$ca['serial'] = 0;
345
		}
346
		$ca_serial = ++$ca['serial'];
347
	}
348

    
349
	$cert_type = cert_type_config_section($type);
350

    
351
	// in case of using Subject Alternative Names use other sections (with postfix '_san')
352
	// pass subjectAltName over environment variable 'SAN'
353
	if ($dn['subjectAltName']) {
354
		putenv("SAN={$dn['subjectAltName']}"); // subjectAltName can be set _only_ via configuration file
355
		$cert_type .= '_san';
356
		unset($dn['subjectAltName']);
357
	}
358

    
359
	$args = array(
360
		"x509_extensions" => $cert_type,
361
		"digest_alg" => $digest_alg,
362
		"encrypt_key" => false);
363
	if ($keytype == 'ECDSA') {
364
		$args["curve_name"] = $ecname;
365
		$args["private_key_type"] = OPENSSL_KEYTYPE_EC;
366
	} else {
367
		$args["private_key_bits"] = (int)$keylen;
368
		$args["private_key_type"] = OPENSSL_KEYTYPE_RSA;
369
	}
370

    
371
	// generate a new key pair
372
	$res_key = openssl_pkey_new($args);
373
	if (!$res_key) {
374
		return false;
375
	}
376

    
377
	// If this is a self-signed cert, blank out the CA and sign with the cert's key
378
	if ($type == "self-signed") {
379
		$ca           = null;
380
		$ca_res_crt   = null;
381
		$ca_res_key   = $res_key;
382
		$ca_serial    = 0;
383
		$cert['type'] = "server";
384
	}
385

    
386
	// generate a certificate signing request
387
	$res_csr = openssl_csr_new($dn, $res_key, $args);
388
	if (!$res_csr) {
389
		return false;
390
	}
391

    
392
	// sign the certificate using an internal CA
393
	$res_crt = openssl_csr_sign($res_csr, $ca_res_crt, $ca_res_key, $lifetime,
394
				 $args, $ca_serial);
395
	if (!$res_crt) {
396
		return false;
397
	}
398

    
399
	// export our certificate data
400
	if (!openssl_pkey_export($res_key, $str_key) ||
401
	    !openssl_x509_export($res_crt, $str_crt)) {
402
		return false;
403
	}
404

    
405
	// return our certificate information
406
	$cert['crt'] = base64_encode($str_crt);
407
	$cert['prv'] = base64_encode($str_key);
408

    
409
	return true;
410
}
411

    
412
function csr_generate(& $cert, $keylen, $dn, $type = "user", $digest_alg = "sha256", $keytype = "RSA", $ecname = "brainpoolP256r1") {
413

    
414
	$cert_type = cert_type_config_section($type);
415

    
416
	// in case of using Subject Alternative Names use other sections (with postfix '_san')
417
	// pass subjectAltName over environment variable 'SAN'
418
	if ($dn['subjectAltName']) {
419
		putenv("SAN={$dn['subjectAltName']}"); // subjectAltName can be set _only_ via configuration file
420
		$cert_type .= '_san';
421
		unset($dn['subjectAltName']);
422
	}
423

    
424
	$args = array(
425
		"x509_extensions" => $cert_type,
426
		"req_extensions" => "req_{$cert_type}",
427
		"digest_alg" => $digest_alg,
428
		"encrypt_key" => false);
429
	if ($keytype == 'ECDSA') {
430
		$args["curve_name"] = $ecname;
431
		$args["private_key_type"] = OPENSSL_KEYTYPE_EC;
432
	} else {
433
		$args["private_key_bits"] = (int)$keylen;
434
		$args["private_key_type"] = OPENSSL_KEYTYPE_RSA;
435
	}
436

    
437
	// generate a new key pair
438
	$res_key = openssl_pkey_new($args);
439
	if (!$res_key) {
440
		return false;
441
	}
442

    
443
	// generate a certificate signing request
444
	$res_csr = openssl_csr_new($dn, $res_key, $args);
445
	if (!$res_csr) {
446
		return false;
447
	}
448

    
449
	// export our request data
450
	if (!openssl_pkey_export($res_key, $str_key) ||
451
	    !openssl_csr_export($res_csr, $str_csr)) {
452
		return false;
453
	}
454

    
455
	// return our request information
456
	$cert['csr'] = base64_encode($str_csr);
457
	$cert['prv'] = base64_encode($str_key);
458

    
459
	return true;
460
}
461

    
462
function csr_sign($csr, & $ca, $duration, $type = "user", $altnames, $digest_alg = "sha256") {
463
	global $config;
464
	$old_err_level = error_reporting(0);
465

    
466
	// Gather the information required for signed cert
467
	$ca_str_crt = base64_decode($ca['crt']);
468
	$ca_str_key = base64_decode($ca['prv']);
469
	$ca_res_key = openssl_pkey_get_private(array(0 => $ca_str_key, 1 => ""));
470
	if (!$ca_res_key) {
471
		return false;
472
	}
473
	if (empty($ca['serial'])) {
474
		$ca['serial'] = 0;
475
	}
476
	$ca_serial = ++$ca['serial'];
477

    
478
	$cert_type = cert_type_config_section($type);
479

    
480
	if (!empty($altnames)) {
481
		putenv("SAN={$altnames}"); // subjectAltName can be set _only_ via configuration file
482
		$cert_type .= '_san';
483
	}
484

    
485
	$args = array(
486
		"x509_extensions" => $cert_type,
487
		"digest_alg" => $digest_alg,
488
		"req_extensions" => "req_{$cert_type}"
489
	);
490

    
491
	// Sign the new cert and export it in x509 format
492
	openssl_x509_export(openssl_csr_sign($csr, $ca_str_crt, $ca_str_key, $duration, $args, $ca_serial), $n509);
493
	error_reporting($old_err_level);
494

    
495
	return $n509;
496
}
497

    
498
function csr_complete(& $cert, $str_crt) {
499
	$str_key = base64_decode($cert['prv']);
500
	cert_import($cert, $str_crt, $str_key);
501
	unset($cert['csr']);
502
	return true;
503
}
504

    
505
function csr_get_subject($str_crt, $decode = true) {
506

    
507
	if ($decode) {
508
		$str_crt = base64_decode($str_crt);
509
	}
510

    
511
	$components = openssl_csr_get_subject($str_crt);
512

    
513
	if (empty($components) || !is_array($components)) {
514
		return "unknown";
515
	}
516

    
517
	ksort($components);
518
	foreach ($components as $a => $v) {
519
		if (!strlen($subject)) {
520
			$subject = "{$a}={$v}";
521
		} else {
522
			$subject = "{$a}={$v}, {$subject}";
523
		}
524
	}
525

    
526
	return $subject;
527
}
528

    
529
function cert_get_subject($str_crt, $decode = true) {
530

    
531
	if ($decode) {
532
		$str_crt = base64_decode($str_crt);
533
	}
534

    
535
	$inf_crt = openssl_x509_parse($str_crt);
536
	$components = $inf_crt['subject'];
537

    
538
	if (empty($components) || !is_array($components)) {
539
		return "unknown";
540
	}
541

    
542
	ksort($components);
543
	foreach ($components as $a => $v) {
544
		if (is_array($v)) {
545
			ksort($v);
546
			foreach ($v as $w) {
547
				$asubject = "{$a}={$w}";
548
				$subject = (strlen($subject)) ? "{$asubject}, {$subject}" : $asubject;
549
			}
550
		} else {
551
			$asubject = "{$a}={$v}";
552
			$subject = (strlen($subject)) ? "{$asubject}, {$subject}" : $asubject;
553
		}
554
	}
555

    
556
	return $subject;
557
}
558

    
559
function cert_get_subject_array($crt) {
560
	$str_crt = base64_decode($crt);
561
	$inf_crt = openssl_x509_parse($str_crt);
562
	$components = $inf_crt['subject'];
563

    
564
	if (!is_array($components)) {
565
		return;
566
	}
567

    
568
	$subject_array = array();
569

    
570
	foreach ($components as $a => $v) {
571
		$subject_array[] = array('a' => $a, 'v' => $v);
572
	}
573

    
574
	return $subject_array;
575
}
576

    
577
function cert_get_subject_hash($crt) {
578
	$str_crt = base64_decode($crt);
579
	$inf_crt = openssl_x509_parse($str_crt);
580
	return $inf_crt['subject'];
581
}
582

    
583
function cert_get_sans($str_crt, $decode = true) {
584
	if ($decode) {
585
		$str_crt = base64_decode($str_crt);
586
	}
587
	$sans = array();
588
	$crt_details = openssl_x509_parse($str_crt);
589
	if (!empty($crt_details['extensions']['subjectAltName'])) {
590
		$sans = explode(',', $crt_details['extensions']['subjectAltName']);
591
	}
592
	return $sans;
593
}
594

    
595
function cert_get_issuer($str_crt, $decode = true) {
596

    
597
	if ($decode) {
598
		$str_crt = base64_decode($str_crt);
599
	}
600

    
601
	$inf_crt = openssl_x509_parse($str_crt);
602
	$components = $inf_crt['issuer'];
603

    
604
	if (empty($components) || !is_array($components)) {
605
		return "unknown";
606
	}
607

    
608
	ksort($components);
609
	foreach ($components as $a => $v) {
610
		if (is_array($v)) {
611
			ksort($v);
612
			foreach ($v as $w) {
613
				$aissuer = "{$a}={$w}";
614
				$issuer = (strlen($issuer)) ? "{$aissuer}, {$issuer}" : $aissuer;
615
			}
616
		} else {
617
			$aissuer = "{$a}={$v}";
618
			$issuer = (strlen($issuer)) ? "{$aissuer}, {$issuer}" : $aissuer;
619
		}
620
	}
621

    
622
	return $issuer;
623
}
624

    
625
/* Works for both RSA and ECC (crt) and key (prv) */
626
function cert_get_publickey($str_crt, $decode = true, $type = "crt") {
627
	if ($decode) {
628
		$str_crt = base64_decode($str_crt);
629
	}
630
	$certfn = tempnam('/tmp', 'CGPK');
631
	file_put_contents($certfn, $str_crt);
632
	switch ($type) {
633
		case 'prv':
634
			exec("/usr/bin/openssl pkey -in {$certfn} -pubout", $out);
635
			break;
636
		case 'crt':
637
			exec("/usr/bin/openssl x509 -in {$certfn} -inform pem -noout -pubkey", $out);
638
			break;
639
		case 'csr':
640
			exec("/usr/bin/openssl req -in {$certfn} -inform pem -noout -pubkey", $out);
641
			break;
642
		default:
643
			$out = array();
644
			break;
645
	}
646
	unlink($certfn);
647
	return implode("\n", $out);
648
}
649

    
650
function cert_get_purpose($str_crt, $decode = true) {
651
	$extended_oids = array(
652
		"1.3.6.1.5.5.8.2.2" => "IP Security IKE Intermediate",
653
	);
654
	if ($decode) {
655
		$str_crt = base64_decode($str_crt);
656
	}
657
	$crt_details = openssl_x509_parse($str_crt);
658
	$purpose = array();
659
	if (!empty($crt_details['extensions']['keyUsage'])) {
660
		$purpose['ku'] = explode(',', $crt_details['extensions']['keyUsage']);
661
		foreach ($purpose['ku'] as & $ku) {
662
			$ku = trim($ku);
663
			if (array_key_exists($ku, $extended_oids)) {
664
				$ku = $extended_oids[$ku];
665
			}
666
		}
667
	} else {
668
		$purpose['ku'] = array();
669
	}
670
	if (!empty($crt_details['extensions']['extendedKeyUsage'])) {
671
		$purpose['eku'] = explode(',', $crt_details['extensions']['extendedKeyUsage']);
672
		foreach ($purpose['eku'] as & $eku) {
673
			$eku = trim($eku);
674
			if (array_key_exists($eku, $extended_oids)) {
675
				$eku = $extended_oids[$eku];
676
			}
677
		}
678
	} else {
679
		$purpose['eku'] = array();
680
	}
681
	$purpose['ca'] = (stristr($crt_details['extensions']['basicConstraints'], 'CA:TRUE') === false) ? 'No': 'Yes';
682
	$purpose['server'] = (in_array('TLS Web Server Authentication', $purpose['eku'])) ? 'Yes': 'No';
683

    
684
	return $purpose;
685
}
686

    
687
function cert_get_ocspstaple($str_crt, $decode = true) {
688
	if ($decode) {
689
		$str_crt = base64_decode($str_crt);
690
	}
691
	$crt_details = openssl_x509_parse($str_crt);
692
	if (($crt_details['extensions']['tlsfeature'] == "status_request") ||
693
	    !empty($crt_details['extensions']['1.3.6.1.5.5.7.1.24'])) {
694
		return true;
695
	}
696
	return false;
697
}
698

    
699
function cert_get_dates($str_crt, $decode = true) {
700
	if ($decode) {
701
		$str_crt = base64_decode($str_crt);
702
	}
703
	$crt_details = openssl_x509_parse($str_crt);
704
	if ($crt_details['validFrom_time_t'] > 0) {
705
		$start = date('r', $crt_details['validFrom_time_t']);
706
	} else {
707
		$dt = DateTime::createFromFormat('ymdHis', rtrim($crt_details['validFrom'], 'Z'));
708
		if ($dt !== false) {
709
			$start = $dt->format(DateTimeInterface::RFC2822);
710
		}
711
	}
712
	if ($crt_details['validTo_time_t'] > 0) {
713
		$end = date('r', $crt_details['validTo_time_t']);
714
	} else {
715
		$dt = DateTime::createFromFormat('ymdHis', rtrim($crt_details['validTo'], 'Z'));
716
		if ($dt !== false) {
717
			$end = $dt->format(DateTimeInterface::RFC2822);
718
		}
719
	}
720
	return array($start, $end);
721
}
722

    
723
function cert_get_serial($str_crt, $decode = true) {
724
	if ($decode) {
725
		$str_crt = base64_decode($str_crt);
726
	}
727
	$crt_details = openssl_x509_parse($str_crt);
728
	if (isset($crt_details['serialNumber'])) {
729
		return $crt_details['serialNumber'];
730
	} else {
731
		return NULL;
732
	}
733
}
734

    
735
function cert_get_sigtype($str_crt, $decode = true) {
736
	if ($decode) {
737
		$str_crt = base64_decode($str_crt);
738
	}
739
	$crt_details = openssl_x509_parse($str_crt);
740

    
741
	$signature = array();
742
	if (isset($crt_details['signatureTypeSN']) && !empty($crt_details['signatureTypeSN'])) {
743
		$signature['shortname'] = $crt_details['signatureTypeSN'];
744
	}
745
	if (isset($crt_details['signatureTypeLN']) && !empty($crt_details['signatureTypeLN'])) {
746
		$signature['longname'] = $crt_details['signatureTypeLN'];
747
	}
748
	if (isset($crt_details['signatureTypeNID']) && !empty($crt_details['signatureTypeNID'])) {
749
		$signature['nid'] = $crt_details['signatureTypeNID'];
750
	}
751

    
752
	return $signature;
753
}
754

    
755
function is_openvpn_server_ca($caref) {
756
	global $config;
757
	if (!is_array($config['openvpn']['openvpn-server'])) {
758
		return;
759
	}
760
	foreach ($config['openvpn']['openvpn-server'] as $ovpns) {
761
		if ($ovpns['caref'] == $caref) {
762
			return true;
763
		}
764
	}
765
	return false;
766
}
767

    
768
function is_openvpn_client_ca($caref) {
769
	global $config;
770
	if (!is_array($config['openvpn']['openvpn-client'])) {
771
		return;
772
	}
773
	foreach ($config['openvpn']['openvpn-client'] as $ovpnc) {
774
		if ($ovpnc['caref'] == $caref) {
775
			return true;
776
		}
777
	}
778
	return false;
779
}
780

    
781
function is_ipsec_peer_ca($caref) {
782
	global $config;
783
	if (!is_array($config['ipsec']['phase1'])) {
784
		return;
785
	}
786
	foreach ($config['ipsec']['phase1'] as $ipsec) {
787
		if ($ipsec['caref'] == $caref) {
788
			return true;
789
		}
790
	}
791
	return false;
792
}
793

    
794
function is_ldap_peer_ca($caref) {
795
	global $config;
796
	if (!is_array($config['system']['authserver'])) {
797
		return;
798
	}
799
	foreach ($config['system']['authserver'] as $authserver) {
800
		if ($authserver['ldap_caref'] == $caref) {
801
			return true;
802
		}
803
	}
804
	return false;
805
}
806

    
807
function ca_in_use($caref) {
808
	return (is_openvpn_server_ca($caref) ||
809
		is_openvpn_client_ca($caref) ||
810
		is_ipsec_peer_ca($caref) ||
811
		is_ldap_peer_ca($caref));
812
}
813

    
814
function is_user_cert($certref) {
815
	global $config;
816
	if (!is_array($config['system']['user'])) {
817
		return;
818
	}
819
	foreach ($config['system']['user'] as $user) {
820
		if (!is_array($user['cert'])) {
821
			continue;
822
		}
823
		foreach ($user['cert'] as $cert) {
824
			if ($certref == $cert) {
825
				return true;
826
			}
827
		}
828
	}
829
	return false;
830
}
831

    
832
function is_openvpn_server_cert($certref) {
833
	global $config;
834
	if (!is_array($config['openvpn']['openvpn-server'])) {
835
		return;
836
	}
837
	foreach ($config['openvpn']['openvpn-server'] as $ovpns) {
838
		if ($ovpns['certref'] == $certref) {
839
			return true;
840
		}
841
	}
842
	return false;
843
}
844

    
845
function is_openvpn_client_cert($certref) {
846
	global $config;
847
	if (!is_array($config['openvpn']['openvpn-client'])) {
848
		return;
849
	}
850
	foreach ($config['openvpn']['openvpn-client'] as $ovpnc) {
851
		if ($ovpnc['certref'] == $certref) {
852
			return true;
853
		}
854
	}
855
	return false;
856
}
857

    
858
function is_ipsec_cert($certref) {
859
	global $config;
860
	if (!is_array($config['ipsec']['phase1'])) {
861
		return;
862
	}
863
	foreach ($config['ipsec']['phase1'] as $ipsec) {
864
		if ($ipsec['certref'] == $certref) {
865
			return true;
866
		}
867
	}
868
	return false;
869
}
870

    
871
function is_webgui_cert($certref) {
872
	global $config;
873
	if (($config['system']['webgui']['ssl-certref'] == $certref) &&
874
	    ($config['system']['webgui']['protocol'] != "http")) {
875
		return true;
876
	}
877
}
878

    
879
function is_package_cert($certref) {
880
	$pluginparams = array();
881
	$pluginparams['type'] = 'certificates';
882
	$pluginparams['event'] = 'used_certificates';
883

    
884
	$certificates_used_by_packages = pkg_call_plugins('plugin_certificates', $pluginparams);
885

    
886
	/* Check if any package is using certificate */
887
	foreach ($certificates_used_by_packages as $name => $package) {
888
		if (is_array($package['certificatelist'][$certref]) &&
889
		    isset($package['certificatelist'][$certref]) > 0) {
890
			return true;
891
		}
892
	}
893
}
894

    
895
function is_captiveportal_cert($certref) {
896
	global $config;
897
	if (!is_array($config['captiveportal'])) {
898
		return;
899
	}
900
	foreach ($config['captiveportal'] as $portal) {
901
		if (isset($portal['enable']) && isset($portal['httpslogin']) && ($portal['certref'] == $certref)) {
902
			return true;
903
		}
904
	}
905
	return false;
906
}
907

    
908
function cert_in_use($certref) {
909

    
910
	return (is_webgui_cert($certref) ||
911
		is_user_cert($certref) ||
912
		is_openvpn_server_cert($certref) ||
913
		is_openvpn_client_cert($certref) ||
914
		is_ipsec_cert($certref) ||
915
		is_captiveportal_cert($certref) ||
916
		is_package_cert($certref));
917
}
918

    
919
function cert_usedby_description($refid, $certificates_used_by_packages) {
920
	$result = "";
921
	if (is_array($certificates_used_by_packages)) {
922
		foreach ($certificates_used_by_packages as $name => $package) {
923
			if (isset($package['certificatelist'][$refid])) {
924
				$hint = "" ;
925
				if (is_array($package['certificatelist'][$refid])) {
926
					foreach ($package['certificatelist'][$refid] as $cert_used) {
927
						$hint = $hint . $cert_used['usedby']."\n";
928
					}
929
				}
930
				$count = count($package['certificatelist'][$refid]);
931
				$result .= "<div title='".htmlspecialchars($hint)."'>";
932
				$result .= htmlspecialchars($package['pkgname'])." ($count)<br />";
933
				$result .= "</div>";
934
			}
935
		}
936
	}
937
	return $result;
938
}
939

    
940
/* Detect a rollover at 2038 on some platforms (e.g. ARM)
941
 * See: https://redmine.pfsense.org/issues/9098 */
942
function crl_get_max_lifetime($max = 9999) {
943
	if ($max <= 0) {
944
		return 0;
945
	}
946
	$current_time = time();
947
	while ((int)($current_time + ($max * 24 * 60 * 60)) < 0) {
948
		$max--;
949
	}
950
	return $max;
951
}
952

    
953
function crl_create(& $crl, $caref, $name, $serial = 0, $lifetime = 3650) {
954
	global $config;
955
	$max_lifetime = crl_get_max_lifetime();
956
	$ca =& lookup_ca($caref);
957
	if (!$ca) {
958
		return false;
959
	}
960
	$crl['descr'] = $name;
961
	$crl['caref'] = $caref;
962
	$crl['serial'] = $serial;
963
	$crl['lifetime'] = ($lifetime > $max_lifetime) ? $max_lifetime : $lifetime;
964
	$crl['cert'] = array();
965
	$config['crl'][] = $crl;
966
	return $crl;
967
}
968

    
969
function crl_update(& $crl) {
970
	require_once('ASN1.php');
971
	require_once('ASN1_UTF8STRING.php');
972
	require_once('ASN1_ASCIISTRING.php');
973
	require_once('ASN1_BITSTRING.php');
974
	require_once('ASN1_BOOL.php');
975
	require_once('ASN1_GENERALTIME.php');
976
	require_once('ASN1_INT.php');
977
	require_once('ASN1_ENUM.php');
978
	require_once('ASN1_NULL.php');
979
	require_once('ASN1_OCTETSTRING.php');
980
	require_once('ASN1_OID.php');
981
	require_once('ASN1_SEQUENCE.php');
982
	require_once('ASN1_SET.php');
983
	require_once('ASN1_SIMPLE.php');
984
	require_once('ASN1_TELETEXSTRING.php');
985
	require_once('ASN1_UTCTIME.php');
986
	require_once('OID.php');
987
	require_once('X509.php');
988
	require_once('X509_CERT.php');
989
	require_once('X509_CRL.php');
990

    
991
	global $config;
992
	$max_lifetime = crl_get_max_lifetime();
993
	$ca =& lookup_ca($crl['caref']);
994
	if (!$ca) {
995
		return false;
996
	}
997
	// If we have text but no certs, it was imported and cannot be updated.
998
	if (($crl["method"] != "internal") && (!empty($crl['text']) && empty($crl['cert']))) {
999
		return false;
1000
	}
1001
	$crl['serial']++;
1002
	$ca_cert = \Ukrbublik\openssl_x509_crl\X509::pem2der(base64_decode($ca['crt']));
1003
	$ca_pkey = openssl_pkey_get_private(base64_decode($ca['prv']));
1004

    
1005
	$crlconf = array(
1006
		'no' => $crl['serial'],
1007
		'version' => 2,
1008
		'days' => ($crl['lifetime'] > $max_lifetime) ? $max_lifetime : $crl['lifetime'],
1009
		'alg' => OPENSSL_ALGO_SHA1,
1010
		'revoked' => array()
1011
	);
1012

    
1013
	if (is_array($crl['cert']) && (count($crl['cert']) > 0)) {
1014
		foreach ($crl['cert'] as $cert) {
1015
			$crlconf['revoked'][] = array(
1016
				'serial' => cert_get_serial($cert["crt"], true),
1017
				'rev_date' => $cert["revoke_time"],
1018
				'reason' => ($cert["reason"] == -1) ? null : (int) $cert["reason"],
1019
			);
1020
		}
1021
	}
1022

    
1023
	$crl_data = \Ukrbublik\openssl_x509_crl\X509_CRL::create($crlconf, $ca_pkey, $ca_cert);
1024
	$crl['text'] = base64_encode(\Ukrbublik\openssl_x509_crl\X509::der2pem4crl($crl_data));
1025

    
1026
	return $crl['text'];
1027
}
1028

    
1029
function cert_revoke($cert, & $crl, $reason = OCSP_REVOKED_STATUS_UNSPECIFIED) {
1030
	global $config;
1031
	if (is_cert_revoked($cert, $crl['refid'])) {
1032
		return true;
1033
	}
1034
	// If we have text but no certs, it was imported and cannot be updated.
1035
	if (!is_crl_internal($crl)) {
1036
		return false;
1037
	}
1038
	$cert["reason"] = $reason;
1039
	$cert["revoke_time"] = time();
1040
	$crl["cert"][] = $cert;
1041
	crl_update($crl);
1042
	return true;
1043
}
1044

    
1045
function cert_unrevoke($cert, & $crl) {
1046
	global $config;
1047
	if (!is_crl_internal($crl)) {
1048
		return false;
1049
	}
1050
	foreach ($crl['cert'] as $id => $rcert) {
1051
		if (($rcert['refid'] == $cert['refid']) || ($rcert['descr'] == $cert['descr'])) {
1052
			unset($crl['cert'][$id]);
1053
			if (count($crl['cert']) == 0) {
1054
				// Protect against accidentally switching the type to imported, for older CRLs
1055
				if (!isset($crl['method'])) {
1056
					$crl['method'] = "internal";
1057
				}
1058
				crl_update($crl);
1059
			} else {
1060
				crl_update($crl);
1061
			}
1062
			return true;
1063
		}
1064
	}
1065
	return false;
1066
}
1067

    
1068
/* Compare two certificates to see if they match. */
1069
function cert_compare($cert1, $cert2) {
1070
	/* Ensure two certs are identical by first checking that their issuers match, then
1071
		subjects, then serial numbers, and finally the moduli. Anything less strict
1072
		could accidentally count two similar, but different, certificates as
1073
		being identical. */
1074
	$c1 = base64_decode($cert1['crt']);
1075
	$c2 = base64_decode($cert2['crt']);
1076
	if ((cert_get_issuer($c1, false) == cert_get_issuer($c2, false)) &&
1077
	    (cert_get_subject($c1, false) == cert_get_subject($c2, false)) &&
1078
	    (cert_get_serial($c1, false) == cert_get_serial($c2, false)) &&
1079
	    (cert_get_publickey($c1, false) == cert_get_publickey($c2, false))) {
1080
		return true;
1081
	}
1082
	return false;
1083
}
1084

    
1085
function is_cert_revoked($cert, $crlref = "") {
1086
	global $config;
1087
	if (!is_array($config['crl'])) {
1088
		return false;
1089
	}
1090

    
1091
	if (!empty($crlref)) {
1092
		$crl = lookup_crl($crlref);
1093
		if (!is_array($crl['cert'])) {
1094
			return false;
1095
		}
1096
		foreach ($crl['cert'] as $rcert) {
1097
			if (cert_compare($rcert, $cert)) {
1098
				return true;
1099
			}
1100
		}
1101
	} else {
1102
		foreach ($config['crl'] as $crl) {
1103
			if (!is_array($crl['cert'])) {
1104
				continue;
1105
			}
1106
			foreach ($crl['cert'] as $rcert) {
1107
				if (cert_compare($rcert, $cert)) {
1108
					return true;
1109
				}
1110
			}
1111
		}
1112
	}
1113
	return false;
1114
}
1115

    
1116
function is_openvpn_server_crl($crlref) {
1117
	global $config;
1118
	if (!is_array($config['openvpn']['openvpn-server'])) {
1119
		return;
1120
	}
1121
	foreach ($config['openvpn']['openvpn-server'] as $ovpns) {
1122
		if (!empty($ovpns['crlref']) && ($ovpns['crlref'] == $crlref)) {
1123
			return true;
1124
		}
1125
	}
1126
	return false;
1127
}
1128

    
1129
// Keep this general to allow for future expansion. See cert_in_use() above.
1130
function crl_in_use($crlref) {
1131
	return (is_openvpn_server_crl($crlref));
1132
}
1133

    
1134
function is_crl_internal($crl) {
1135
	return (!(!empty($crl['text']) && empty($crl['cert'])) || ($crl["method"] == "internal"));
1136
}
1137

    
1138
function cert_get_cn($crt, $isref = false) {
1139
	/* If this is a certref, not an actual cert, look up the cert first */
1140
	if ($isref) {
1141
		$cert = lookup_cert($crt);
1142
		/* If it's not a valid cert, bail. */
1143
		if (!(is_array($cert) && !empty($cert['crt']))) {
1144
			return "";
1145
		}
1146
		$cert = $cert['crt'];
1147
	} else {
1148
		$cert = $crt;
1149
	}
1150
	$sub = cert_get_subject_array($cert);
1151
	if (is_array($sub)) {
1152
		foreach ($sub as $s) {
1153
			if (strtoupper($s['a']) == "CN") {
1154
				return $s['v'];
1155
			}
1156
		}
1157
	}
1158
	return "";
1159
}
1160

    
1161
function cert_escape_x509_chars($str, $reverse = false) {
1162
	/* Characters which need escaped when present in x.509 fields.
1163
	 * See https://www.ietf.org/rfc/rfc4514.txt
1164
	 *
1165
	 * The backslash (\) must be listed first in these arrays!
1166
	 */
1167
	$cert_directory_string_special_chars = array('\\', '"', '#', '+', ',', ';', '<', '=', '>');
1168
	$cert_directory_string_special_chars_esc = array('\\\\', '\"', '\#', '\+', '\,', '\;', '\<', '\=', '\>');
1169
	if ($reverse) {
1170
		return str_replace($cert_directory_string_special_chars_esc, $cert_directory_string_special_chars, $str);
1171
	} else {
1172
		/* First unescape and then escape again, to prevent possible double escaping. */
1173
		return str_replace($cert_directory_string_special_chars, $cert_directory_string_special_chars_esc, cert_escape_x509_chars($str, true));
1174
	}
1175
}
1176

    
1177
function cert_add_altname_type($str) {
1178
	$type = "";
1179
	if (is_ipaddr($str)) {
1180
		$type = "IP";
1181
	} elseif (is_hostname($str, true)) {
1182
		$type = "DNS";
1183
	} elseif (is_URL($str)) {
1184
		$type = "URI";
1185
	} elseif (filter_var($str, FILTER_VALIDATE_EMAIL)) {
1186
		$type = "email";
1187
	}
1188
	if (!empty($type)) {
1189
		return "{$type}:" . cert_escape_x509_chars($str);
1190
	} else {
1191
		return null;
1192
	}
1193
}
1194

    
1195
function cert_type_config_section($type) {
1196
	switch ($type) {
1197
		case "ca":
1198
			$cert_type = "v3_ca";
1199
			break;
1200
		case "server":
1201
		case "self-signed":
1202
			$cert_type = "server";
1203
			break;
1204
		default:
1205
			$cert_type = "usr_cert";
1206
			break;
1207
	}
1208
	return $cert_type;
1209
}
1210

    
1211
/****f* certs/is_cert_locally_renewable
1212
 * NAME
1213
 *   is_cert_locally_renewable - Check to see if an existing certificate can be
1214
 *                               renewed by a local internal CA.
1215
 * INPUTS
1216
 *   $cert : The certificate to be tested
1217
 * RESULT
1218
 *   true if the certificate can be locally renewed, false otherwise.
1219
 ******/
1220

    
1221
function is_cert_locally_renewable($cert) {
1222
	/* If there is no certificate or private key string, this entry is either
1223
	 * invalid or cannot be renewed. */
1224
	if (empty($cert['crt']) || empty($cert['prv'])) {
1225
		return false;
1226
	}
1227

    
1228
	/* Get subject and issuer values to test for self-signed state */
1229
	$subj = cert_get_subject($cert['crt']);
1230
	$issuer = cert_get_issuer($cert['crt']);
1231

    
1232
	/* Lookup CA for this certificate */
1233
	$ca = array();
1234
	if (!empty($cert['caref'])) {
1235
		$ca = lookup_ca($cert['caref']);
1236
	}
1237

    
1238
	/* If the CA exists and we have the private key, or if the cert is
1239
	 *  self-signed, then it can be locally renewed. */
1240
	return ((!empty($ca) && !empty($ca['prv'])) || ($subj == $issuer));
1241
}
1242

    
1243
/* Strict certificate requirements based on
1244
 * https://redmine.pfsense.org/issues/9825
1245
 */
1246
global $cert_strict_values;
1247
$cert_strict_values = array(
1248
	'max_server_cert_lifetime' => 825,
1249
	'digest_blacklist' => array('md4', 'RSA-MD4',  'md5', 'RSA-MD5', 'md5-sha1',
1250
					'mdc2', 'RSA-MDC2', 'sha1', 'RSA-SHA1',
1251
					'RSA-SHA1-2'),
1252
	'min_private_key_bits' => 2048,
1253
);
1254

    
1255
/****f* certs/cert_renew
1256
 * NAME
1257
 *   cert_renew - Renew an existing internal CA or certificate
1258
 * INPUTS
1259
 *   $cert : The entry to be renewed (used as a reference so it can be altered directly)
1260
 *   $reusekey : Whether or not to reuse the existing key for the certificate
1261
 *      true: Reuse the existing key (Default)
1262
 *      false: Generate a new key based on current (or enforced minimum) parameters
1263
 *   $strictsecurity : Whether or not to enforce stricter security for specific attributes
1264
 *      true: Enforce maximum lifetime for server certs, minimum digest type, and
1265
 *            minimum private key size. See https://redmine.pfsense.org/issues/9825
1266
 *      false: Use existing values as-is (Default).
1267
 * RESULT
1268
 *   true if successful, false if failure.
1269
 * NOTES
1270
 *   See https://redmine.pfsense.org/issues/9842 for more information on behavior.
1271
 *   Does NOT run write_config(), that must be performed by the caller.
1272
 ******/
1273

    
1274
function cert_renew(& $cert, $reusekey = true, $strictsecurity = false) {
1275
	global $cert_strict_values;
1276

    
1277
	/* If there is no certificate or private key string, this entry is either
1278
	 *  invalid or cannot be renewed by this function. */
1279
	if (empty($cert['crt']) || empty($cert['prv'])) {
1280
		return false;
1281
	}
1282

    
1283
	/* Read certificate information necessary to create a new request */
1284
	$cert_details = openssl_x509_parse(base64_decode($cert['crt']));
1285

    
1286
	/* No details, must not be valid in some way */
1287
	if (!array($cert_details) || empty($cert_details)) {
1288
		return false;
1289
	}
1290

    
1291
	$subj = cert_get_subject($cert['crt']);
1292
	$issuer = cert_get_issuer($cert['crt']);
1293

    
1294
	$res_key = openssl_pkey_get_private(base64_decode($cert['prv']));
1295
	$key_details = openssl_pkey_get_details($res_key);
1296

    
1297
	/* Form a new Distinguished Name from the existing values.
1298
	 * Note: Deprecated/unsupported DN fields may not be carried forward, but
1299
	 *       may be preserved to avoid altering a subject.
1300
	 */
1301
	$subject_map = array(
1302
		'CN' => 'commonName',
1303
		'C' => 'countryName',
1304
		'ST' => 'stateOrProvinceName',
1305
		'L' => 'localityName',
1306
		'O' => 'organizationName',
1307
		'OU' => 'organizationalUnitName',
1308
		'emailAddress' => 'emailAddress', /* deprecated, but commonly found in older entries */
1309
	);
1310
	$dn = array();
1311
	/* This is necessary to ensure the order of subject components is
1312
	 * identical on the old and new certificate. */
1313
	foreach ($cert_details['subject'] as $p => $v) {
1314
		if (array_key_exists($p, $subject_map)) {
1315
			$dn[$subject_map[$p]] = $v;
1316
		}
1317
	}
1318

    
1319
	/* Test for self-signed or signed by a CA */
1320
	$selfsigned = ($subj == $issuer);
1321

    
1322
	/* Determine the type if it is not specified directly */
1323
	if (array_key_exists('serial', $cert)) {
1324
		/* If a serial value is present, this must be a CA */
1325
		$cert['type'] = 'ca';
1326
	} elseif (empty($cert['type'])) {
1327
		/* Assume server certificate if empty & non-CA (e.g. self-signed) */
1328
		$cert['type'] = 'server';
1329
	}
1330

    
1331
	/* Convert the internal certificate type to an openssl.cnf section name */
1332
	$cert_type = cert_type_config_section($cert['type']);
1333
	if ($cert['type'] != 'ca') {
1334
		$cert_type .= '_san';
1335
	}
1336

    
1337
	/* Reuse lifetime (convert seconds to days) */
1338
	$lifetime = (int) round(($cert_details['validTo_time_t'] - $cert_details['validFrom_time_t']) / 86400);
1339

    
1340
	/* If we are enforcing strict security, then cap the lifetime for server certificates */
1341
	if (($cert_type == 'server_san') && $strictsecurity &&
1342
	    ($lifetime > $cert_strict_values['max_server_cert_lifetime'])) {
1343
		$lifetime = $cert_strict_values['max_server_cert_lifetime'];
1344
	}
1345

    
1346
	/* Reuse SAN list, or, if empty, add CN as SAN. */
1347
	$sans = $cert_details['extensions']['subjectAltName'];
1348
	if (empty($sans)) {
1349
		$sans = cert_add_altname_type($dn['commonName']);
1350
	}
1351

    
1352
	/* subjectAltName can be set _only_ via configuration file, so put the
1353
	 * value into the environment where it will be read from the configuration */
1354
	putenv("SAN={$sans}");
1355

    
1356
	/* If we are enforcing strict security, then check the digest against a
1357
	 * blacklist of insecure digest methods. */
1358
	$digest_alg = $cert_details['signatureTypeSN'];
1359
	if ($strictsecurity &&
1360
	    (in_array($digest_alg, $cert_strict_values['digest_blacklist']))) {
1361
		$digest_alg = 'sha256';
1362
	}
1363

    
1364
	/* If we are enforcing strict security, then ensure the private key size
1365
	 * is at least 2048 bits. */
1366
	$private_key_bits = $key_details['bits'];
1367
	if ($strictsecurity &&
1368
	    ($private_key_bits < $cert_strict_values['min_private_key_bits'])) {
1369
		$private_key_bits = $cert_strict_values['min_private_key_bits'];
1370
		$reusekey = false;
1371
	}
1372

    
1373
	/* Validate key type, assume RSA if it cannot be read. */
1374
	if (is_array($key_details) && array_key_exists('type', $key_details)) {
1375
		$private_key_type = $key_details['type'];
1376
	} else {
1377
		$private_key_type = OPENSSL_KEYTYPE_RSA;
1378
	}
1379

    
1380
	/* Setup certificate and key arguments */
1381
	$args = array(
1382
		"x509_extensions" => $cert_type,
1383
		"digest_alg" => $digest_alg,
1384
		"private_key_bits" => (int)$private_key_bits,
1385
		"private_key_type" => $private_key_type,
1386
		"encrypt_key" => false);
1387

    
1388
	/* Adjust argumnents to account for ECDSA keys. */
1389
	if (($key_details['type'] ==  OPENSSL_KEYTYPE_EC) &&
1390
	    (!empty($key_details['ec']['curve_name']))) {
1391
		$args['curve_name'] = $key_details['ec']['curve_name'];
1392
	}
1393

    
1394
	/* Make a new key if necessary */
1395
	if (!$res_key || !$reusekey) {
1396
		$res_key = openssl_pkey_new($args);
1397
		if (!$res_key) {
1398
			return false;
1399
		}
1400
	}
1401

    
1402
	/* Create a new CSR from derived parameters and key */
1403
	$res_csr = openssl_csr_new($dn, $res_key, $args);
1404
	/* If the CSR could not be created, bail */
1405
	if (!$res_csr) {
1406
		return false;
1407
	}
1408

    
1409
	if (!empty($cert['caref'])) {
1410
		/* The certificate was signed by a CA, so read the CA details. */
1411
		$ca = & lookup_ca($cert['caref']);
1412
		/* If the referenced CA cannot be found, bail. */
1413
		if (!$ca) {
1414
			return false;
1415
		}
1416
		$ca_str_crt = base64_decode($ca['crt']);
1417
		$ca_str_key = base64_decode($ca['prv']);
1418
		$ca_res_crt = openssl_x509_read($ca_str_crt);
1419
		$ca_res_key = openssl_pkey_get_private(array(0 => $ca_str_key, 1 => ""));
1420
		if (!$ca_res_key) {
1421
			/* If the CA key cannot be read, bail. */
1422
			return false;
1423
		}
1424
		/* If the CA does not have a serial number, assume 0. */
1425
		if (empty($ca['serial'])) {
1426
			$ca['serial'] = 0;
1427
		}
1428
		/* Increment the CA serial before use. */
1429
		$ca_serial = ++$ca['serial'];
1430
	} elseif ($selfsigned) {
1431
		/* For self-signed CAs & certificates, set the CA details to self and
1432
		 * use the key for this entry to sign itself.
1433
		 */
1434
		$ca_res_crt   = null;
1435
		$ca_res_key   = $res_key;
1436
		$ca_serial    = 0; /* TODO: Check if we should increment from previous */
1437
	}
1438

    
1439
	/* Sign the CSR */
1440
	$res_crt = openssl_csr_sign($res_csr, $ca_res_crt, $ca_res_key, $lifetime,
1441
				 $args, $ca_serial);
1442
	/* If the CSR could not be signed, bail */
1443
	if (!$res_crt) {
1444
		return false;
1445
	}
1446

    
1447
	/* Attempt to read the key and certificate and if that fails, bail */
1448
	if (!openssl_pkey_export($res_key, $str_key) ||
1449
	    !openssl_x509_export($res_crt, $str_crt)) {
1450
		return false;
1451
	}
1452

    
1453
	/* Load the new certificate string and key into the configuration */
1454
	$cert['crt'] = base64_encode($str_crt);
1455
	$cert['prv'] = base64_encode($str_key);
1456

    
1457
	return true;
1458
}
1459

    
1460
/****f* certs/cert_get_all_services
1461
 * NAME
1462
 *   cert_get_all_services - Locate services using a given certificate
1463
 * INPUTS
1464
 *   $refid: The refid of a certificate to check
1465
 * RESULT
1466
 *   array containing the services which use this certificate, including:
1467
 *     webgui: Present and true if the WebGUI uses this certificate. Unset otherwise.
1468
 *     services: Array of service definitions using this certificate, with:
1469
 *       name: Name of the service
1470
 *       extras: Extra information needed by some services, such as OpenVPN or Captive Portal.
1471
 *     packages: Array containing package names using this certificate.
1472
 ******/
1473

    
1474
function cert_get_all_services($refid) {
1475
	global $config;
1476
	$services = array();
1477
	$services['services'] = array();
1478
	$services['packages'] = array();
1479

    
1480
	/* Only set if true, otherwise leave unset. */
1481
	if (is_webgui_cert($refid)) {
1482
		$services['webgui'] = true;
1483
	}
1484

    
1485
	init_config_arr(array('openvpn', 'openvpn-server'));
1486
	init_config_arr(array('openvpn', 'openvpn-client'));
1487
	/* Find all OpenVPN clients and servers which use this certificate */
1488
	foreach(array('server', 'client') as $mode) {
1489
		foreach ($config['openvpn']["openvpn-{$mode}"] as $ovpn) {
1490
			if ($ovpn['certref'] == $refid) {
1491
				/* OpenVPN instances are restarted individually,
1492
				 * so we need to note the mode and ID. */
1493
				$services['services'][] = array(
1494
					'name' => 'openvpn',
1495
					'extras' => array(
1496
						'vpnmode' => $mode,
1497
						'id' => $ovpn['vpnid']
1498
					)
1499
				);
1500
			}
1501
		}
1502
	}
1503

    
1504
	/* If any one IPsec tunnel uses this certificate then the whole service
1505
	 * needs a bump. */
1506
	init_config_arr(array('ipsec', 'phase1'));
1507
	foreach ($config['ipsec']['phase1'] as $ipsec) {
1508
		if (($ipsec['authentication_method'] == 'rsasig') &&
1509
		    ($ipsec['certref'] == $refid)) {
1510
			$services['services'][] = array('name' => 'ipsec');
1511
			/* Stop after finding one, no need to search for more. */
1512
			break;
1513
		}
1514
	}
1515

    
1516
	/* Check to see if any HTTPS-enabled Captive Portal zones use this
1517
	 * certificate. */
1518
	init_config_arr(array('captiveportal'));
1519
	foreach ($config['captiveportal'] as $zone => $portal) {
1520
		if (isset($portal['enable']) && isset($portal['httpslogin']) &&
1521
		    ($portal['certref'] == $refid)) {
1522
			/* Captive Portal zones are restarted individually, so
1523
			 * we need to note the zone name. */
1524
			$services['services'][] = array(
1525
				'name' => 'captiveportal',
1526
				'extras' => array(
1527
					'zone' => $zone,
1528
				)
1529
			);
1530
		}
1531
	}
1532

    
1533
	/* Locate any packages using this certificate */
1534
	$pkgcerts = pkg_call_plugins('plugin_certificates', array('type' => 'certificates', 'event' => 'used_certificates'));
1535
	foreach ($pkgcerts as $name => $package) {
1536
		if (is_array($package['certificatelist'][$refid]) &&
1537
		    isset($package['certificatelist'][$refid]) > 0) {
1538
			$services['packages'][] = $name;
1539
		}
1540
	}
1541

    
1542
	return $services;
1543
}
1544

    
1545
/****f* certs/ca_get_all_services
1546
 * NAME
1547
 *   ca_get_all_services - Locate services using a given certificate authority or its decendents
1548
 * INPUTS
1549
 *   $refid: The refid of a certificate authority to check
1550
 * RESULT
1551
 *   array containing the services which use this certificate authority, including:
1552
 *     webgui: Present and true if the WebGUI uses this certificate. Unset otherwise.
1553
 *     services: Array of service definitions using this certificate, with:
1554
 *       name: Name of the service
1555
 *       extras: Extra information needed by some services, such as OpenVPN or Captive Portal.
1556
 *     packages: Array containing package names using this certificate.
1557
 * NOTES
1558
 *   This searches recursively to find entries using this CA as well as intermediate
1559
 *   CAs and certificates signed by this CA, and returns a single set of all services.
1560
 *   This avoids restarting affected services multiple times when there is overlapping
1561
 *   usage.
1562
 ******/
1563
function ca_get_all_services($refid) {
1564
	global $config;
1565
	$services = array();
1566
	$services['services'] = array();
1567

    
1568
	init_config_arr(array('openvpn', 'openvpn-server'));
1569
	init_config_arr(array('openvpn', 'openvpn-client'));
1570
	foreach(array('server', 'client') as $mode) {
1571
		foreach ($config['openvpn']["openvpn-{$mode}"] as $ovpn) {
1572
			if ($ovpn['caref'] == $refid) {
1573
				$services['services'][] = array(
1574
					'name' => 'openvpn',
1575
					'extras' => array(
1576
						'vpnmode' => $mode,
1577
						'id' => $ovpn['vpnid']
1578
					)
1579
				);
1580
			}
1581
		}
1582
	}
1583
	init_config_arr(array('ipsec', 'phase1'));
1584
	foreach ($config['ipsec']['phase1'] as $ipsec) {
1585
		if ($ipsec['certref'] == $refid) {
1586
			break;
1587
		}
1588
	}
1589
	foreach ($config['ipsec']['phase1'] as $ipsec) {
1590
		if (($ipsec['authentication_method'] == 'rsasig') &&
1591
		    ($ipsec['caref'] == $refid)) {
1592
			$services['services'][] = array('name' => 'ipsec');
1593
			break;
1594
		}
1595
	}
1596

    
1597
	/* Loop through all certs and get their services as well */
1598
	init_config_arr(array('cert'));
1599
	foreach ($config['cert'] as $cert) {
1600
		if ($cert['caref'] == $refid) {
1601
			$services = array_merge_recursive_unique($services, cert_get_all_services($cert['refid']));
1602
		}
1603
	}
1604

    
1605
	/* Look for intermediate certs and services */
1606
	init_config_arr(array('ca'));
1607
	foreach ($config['ca'] as $cert) {
1608
		if ($cert['caref'] == $refid) {
1609
			$services = array_merge_recursive_unique($services, ca_get_all_services($cert['refid']));
1610
		}
1611
	}
1612

    
1613
	return $services;
1614
}
1615

    
1616
/****f* certs/cert_restart_services
1617
 * NAME
1618
 *   cert_restart_services - Restarts services specific to CA/Certificate usage
1619
 * INPUTS
1620
 *   $services: An array of services returned by cert_get_all_services or ca_get_all_services
1621
 * RESULT
1622
 *   Services in the given array are restarted
1623
 *   returns false if the input is invalid
1624
 *   returns true at the end of execution
1625
 ******/
1626

    
1627
function cert_restart_services($services) {
1628
	/* If the input is not an array, it is invalid. */
1629
	if (!is_array($services)) {
1630
		return false;
1631
	}
1632

    
1633
	/* Base string to log when restarting a service */
1634
	$restart_string = gettext('Restarting %s %s due to certificate change');
1635

    
1636
	/* Restart GUI: */
1637
	if ($services['webgui']) {
1638
		ob_flush();
1639
		flush();
1640
		log_error(sprintf($restart_string, gettext('service'), 'WebGUI'));
1641
		send_event("service restart webgui");
1642
	}
1643

    
1644
	/* Restart other base services: */
1645
	if (is_array($services['services'])) {
1646
		foreach ($services['services'] as $service) {
1647
			switch ($service['name']) {
1648
				case 'openvpn':
1649
					$service_name = "{$service['name']} {$service['extras']['vpnmode']} {$service['extras']['id']}";
1650
					break;
1651
				case 'captiveportal':
1652
					$service_name = "{$service['name']} zone {$service['extras']['zone']}";
1653
					break;
1654
				default:
1655
					$service_name = $service['name'];
1656
			}
1657
			log_error(sprintf($restart_string, gettext('service'), $service_name));
1658
			service_control_restart($service['name'], $service['extras']);
1659
		}
1660
	}
1661

    
1662
	/* Restart Packages: */
1663
	if (is_array($services['packages'])) {
1664
		foreach ($services['packages'] as $service) {
1665
			log_error(sprintf($restart_string, gettext('package'), $service));
1666
			restart_service($service);
1667
		}
1668
	}
1669
	return true;
1670
}
1671

    
1672
/****f* certs/cert_get_lifetime
1673
 * NAME
1674
 *   cert_get_lifetime - Returns the number of days the certificate is valid
1675
 * INPUTS
1676
 *   $untilexpire: Boolean
1677
 *     true: The number of days returned is from now until the certificate expiration.
1678
 *     false (default): The number of days returned is the total lifetime of the certificate.
1679
 * RESULT
1680
 *   Integer number of days in the certificate total or remaining lifetime
1681
 ******/
1682

    
1683
function cert_get_lifetime($cert, $untilexpire = false) {
1684
	/* If the certificate is not valid, bail. */
1685
	if (!is_array($cert) || empty($cert['crt'])) {
1686
		return null;
1687
	}
1688
	/* Read certificate details */
1689
	$cert_details = openssl_x509_parse(base64_decode($cert['crt']));
1690
	/* Determine which start time to use (now, or cert start) */
1691
	$fromtime = ($untilexpire) ? time() : $cert_details['validFrom_time_t'];
1692
	/* Calculate and return the requested duration, converting from seconds to days. */
1693
	return (int) round(($cert_details['validTo_time_t'] - $fromtime) / 86400);
1694
}
1695

    
1696
/****f* certs/cert_analyze_lifetime
1697
 * NAME
1698
 *   cert_analyze_lifetime - Analyze a certificate lifetime for expiration notices
1699
 * INPUTS
1700
 *   $expiredays: Number of days until the certificate expires (See cert_get_lifetime())
1701
 * RESULT
1702
 *   An array of two entries:
1703
 *   0/$lrclass: A bootstrap name for use with classes like text-<x>
1704
 *   1/$expstring: A text analysis describing the expiration timeframe.
1705
 ******/
1706

    
1707
function cert_analyze_lifetime($expiredays) {
1708
	global $config, $g;
1709
	/* Number of days at which to warn of expiration. */
1710
	init_config_arr(array('notifications', 'certexpire'));
1711
	if (!isset($config['notifications']['certexpire']['expiredays']) ||
1712
	    empty($config['notifications']['certexpire']['expiredays'])) {
1713
		$warning_days = $g['default_cert_expiredays'];
1714
	} else {
1715
		$warning_days = $config['notifications']['certexpire']['expiredays'];
1716
	}
1717

    
1718
	if ($expiredays > $warning_days) {
1719
		/* Not expiring soon */
1720
		$lrclass = 'normal';
1721
		$expstring = gettext("%d %s until expiration");
1722
	} elseif ($expiredays > 0) {
1723
		/* Still valid but expiring soon */
1724
		$lrclass = 'warning';
1725
		$expstring = gettext("Expiring soon, in %d %s");
1726
	} else {
1727
		/* Certificate has expired */
1728
		$lrclass = 'danger';
1729
		$expstring = gettext("Expired %d %s ago");
1730
	}
1731
	$days = (abs($expiredays) == 1) ? gettext('day') : gettext('days');
1732
	$expstring = sprintf($expstring, abs($expiredays), $days);
1733
	return array($lrclass, $expstring);
1734
}
1735

    
1736
/****f* certs/cert_print_dates
1737
 * NAME
1738
 *   cert_print_dates - Print the start and end timestamps for the given certificate
1739
 * INPUTS
1740
 *   $cert: CA or Cert entry for which the dates will be printed
1741
 * RESULT
1742
 *   Returns null if the passed entry is invalid
1743
 *   Otherwise, outputs the dates to the user with formatting.
1744
 ******/
1745

    
1746
function cert_print_dates($cert) {
1747
	/* If the certificate is not valid, bail. */
1748
	if (!is_array($cert) || empty($cert['crt'])) {
1749
		return null;
1750
	}
1751
	/* Attempt to extract the dates from the certificate */
1752
	list($startdate, $enddate) = cert_get_dates($cert['crt']);
1753
	/* If either of the timestamps are empty, then do not print anything.
1754
	 * The entry may not be valid or it may just be missing date information */
1755
	if (empty($startdate) || empty($enddate)) {
1756
		return null;
1757
	}
1758
	/* Get the expiration days */
1759
	$expiredays = cert_get_lifetime($cert, true);
1760
	/* Analyze the lifetime value */
1761
	list($lrclass, $expstring) = cert_analyze_lifetime($expiredays);
1762
	/* Output the dates, with a tooltip showing days until expiration, and
1763
	 * a visual indication of warning/expired status. */
1764
	?>
1765
	<br />
1766
	<small>
1767
	<?=gettext("Valid From")?>: <b><?=$startdate ?></b><br />
1768
	<?=gettext("Valid Until")?>:
1769
	<span class="text-<?=$lrclass?>" data-toggle="tooltip" data-placement="bottom" title="<?= $expstring ?>">
1770
	<b><?=$enddate ?></b>
1771
	</span>
1772
	</small>
1773
	<?php
1774
}
1775

    
1776
/****f* certs/cert_print_infoblock
1777
 * NAME
1778
 *   cert_print_infoblock - Print an information block containing certificate details
1779
 * INPUTS
1780
 *   $cert: CA or Cert entry for which the information will be printed
1781
 * RESULT
1782
 *   Returns null if the passed entry is invalid
1783
 *   Otherwise, outputs information to the user with formatting.
1784
 ******/
1785

    
1786
function cert_print_infoblock($cert) {
1787
	/* If the certificate is not valid, bail. */
1788
	if (!is_array($cert) || empty($cert['crt'])) {
1789
		return null;
1790
	}
1791
	/* Variable to hold the formatted information */
1792
	$certextinfo = "";
1793

    
1794
	/* Serial number */
1795
	$cert_details = openssl_x509_parse(base64_decode($cert['crt']));
1796
	if (isset($cert_details['serialNumber']) && (strlen($cert_details['serialNumber']) > 0)) {
1797
		$certextinfo .= '<b>' . gettext("Serial: ") . '</b> ';
1798
		$certextinfo .= htmlspecialchars(cert_escape_x509_chars($cert_details['serialNumber'], true));
1799
		$certextinfo .= '<br/>';
1800
	}
1801

    
1802
	/* Digest type */
1803
	$certsig = cert_get_sigtype($cert['crt']);
1804
	if (is_array($certsig) && !empty($certsig) && !empty($certsig['shortname'])) {
1805
		$certextinfo .= '<b>' . gettext("Signature Digest: ") . '</b> ';
1806
		$certextinfo .= htmlspecialchars(cert_escape_x509_chars($certsig['shortname'], true));
1807
		$certextinfo .= '<br/>';
1808
	}
1809

    
1810
	/* Subject Alternative Name (SAN) list */
1811
	$sans = cert_get_sans($cert['crt']);
1812
	if (is_array($sans) && !empty($sans)) {
1813
		$certextinfo .= '<b>' . gettext("SAN: ") . '</b> ';
1814
		$certextinfo .= htmlspecialchars(implode(', ', cert_escape_x509_chars($sans, true)));
1815
		$certextinfo .= '<br/>';
1816
	}
1817

    
1818
	/* Key usage */
1819
	$purpose = cert_get_purpose($cert['crt']);
1820
	if (is_array($purpose) && !empty($purpose['ku'])) {
1821
		$certextinfo .= '<b>' . gettext("KU: ") . '</b> ';
1822
		$certextinfo .= htmlspecialchars(implode(', ', $purpose['ku']));
1823
		$certextinfo .= '<br/>';
1824
	}
1825

    
1826
	/* Extended key usage */
1827
	if (is_array($purpose) && !empty($purpose['eku'])) {
1828
		$certextinfo .= '<b>' . gettext("EKU: ") . '</b> ';
1829
		$certextinfo .= htmlspecialchars(implode(', ', $purpose['eku']));
1830
		$certextinfo .= '<br/>';
1831
	}
1832

    
1833
	/* OCSP / Must Staple */
1834
	if (cert_get_ocspstaple($cert['crt'])) {
1835
		$certextinfo .= '<b>' . gettext("OCSP: ") . '</b> ';
1836
		$certextinfo .= gettext("Must Staple");
1837
		$certextinfo .= '<br/>';
1838
	}
1839

    
1840
	/* Private key information */
1841
	if (!empty($cert['prv'])) {
1842
		$res_key = openssl_pkey_get_private(base64_decode($cert['prv']));
1843
		$key_details = openssl_pkey_get_details($res_key);
1844

    
1845
		/* Key type (RSA or EC) */
1846
		$certextinfo .= '<b>' . gettext("Key Type: ") . '</b> ';
1847
		if ($key_details['type'] == OPENSSL_KEYTYPE_RSA) {
1848
			/* RSA Key size */
1849
			$certextinfo .= 'RSA<br/>';
1850
			$certextinfo .= '<b>' . gettext("Key Size: ") . '</b> ';
1851
			$certextinfo .= $key_details['bits'] . '<br/>';
1852
		} else {
1853
			/* Elliptic curve (EC) key curve name */
1854
			$certextinfo .= 'ECDSA<br/>';
1855
			$certextinfo .= '<b>' . gettext("Elliptic curve name: ") . '</b>';
1856
			$certextinfo .= $key_details['ec']['curve_name'] . '<br/>';
1857
		}
1858
	}
1859

    
1860
	/* Distinguished name (DN) */
1861
	if (!empty($cert_details['name'])) {
1862
		$certextinfo .= '<b>' . gettext("DN: ") . '</b> ';
1863
		$certextinfo .= htmlspecialchars(cert_escape_x509_chars($cert_details['name'], true));
1864
		$certextinfo .= '<br/>';
1865
	}
1866

    
1867
	/* Hash value */
1868
	if (!empty($cert_details['hash'])) {
1869
		$certextinfo .= '<b>' . gettext("Hash: ") . '</b> ';
1870
		$certextinfo .= htmlspecialchars(cert_escape_x509_chars($cert_details['hash'], true));
1871
		$certextinfo .= '<br/>';
1872
	}
1873

    
1874
	/* Subject Key Identifier (SKID) */
1875
	if (is_array($cert_details["extensions"]) && !empty($cert_details["extensions"]["subjectKeyIdentifier"])) {
1876
		$certextinfo .= '<b>' . gettext("Subject Key ID: ") . '</b> ';
1877
		$certextinfo .= htmlspecialchars(cert_escape_x509_chars($cert_details["extensions"]["subjectKeyIdentifier"], true));
1878
		$certextinfo .= '<br/>';
1879
	}
1880

    
1881
	/* Authority Key Identifier (AKID) */
1882
	if (is_array($cert_details["extensions"]) && !empty($cert_details["extensions"]["authorityKeyIdentifier"])) {
1883
		$certextinfo .= '<b>' . gettext("Authority Key ID: ") . '</b> ';
1884
		$certextinfo .= str_replace("\n", '<br/>', htmlspecialchars(cert_escape_x509_chars($cert_details["extensions"]["authorityKeyIdentifier"], true)));
1885
		$certextinfo .= '<br/>';
1886
	}
1887

    
1888
	/* Total Lifetime (days from cert start to end) */
1889
	$lifetime = cert_get_lifetime($cert);
1890
	$certextinfo .= '<b>' . gettext("Total Lifetime: ") . '</b> ';
1891
	$certextinfo .= sprintf("%d %s", $lifetime, (abs($lifetime) == 1) ? gettext('day') : gettext('days'));
1892
	$certextinfo .= '<br/>';
1893

    
1894
	/* Lifetime before certificate expires (days from now to end) */
1895
	$expiredays = cert_get_lifetime($cert, true);
1896
	list($lrclass, $expstring) = cert_analyze_lifetime($expiredays);
1897
	$certextinfo .= '<b>' . gettext("Lifetime Remaining: ") . '</b> ';
1898
	$certextinfo .= "<span class=\"text-{$lrclass}\">{$expstring}</span>";
1899
	$certextinfo .= '<br/>';
1900

    
1901
	/* Output the infoblock */
1902
	if (!empty($certextinfo)) { ?>
1903
		<div class="infoblock">
1904
		<? print_info_box($certextinfo, 'info', false); ?>
1905
		</div>
1906
	<?php
1907
	}
1908
}
1909

    
1910
/****f* certs/cert_notify_expiring
1911
 * NAME
1912
 *   cert_notify_expiring - Notify admin about expiring certificates
1913
 * INPUTS
1914
 *   None
1915
 * RESULT
1916
 *   File a notice containing expiring certificate information, which is then
1917
 *   logged, displayed in the GUI, and sent via e-mail (if enabled).
1918
 ******/
1919

    
1920
function cert_notify_expiring() {
1921
	global $config;
1922

    
1923
	/* If certificate expiration notifications are disabled, there is nothing to do. */
1924
	init_config_arr(array('notifications', 'certexpire'));
1925
	if ($config['notifications']['certexpire']['enable'] == "disable") {
1926
		return;
1927
	}
1928

    
1929
	$notifications = array();
1930

    
1931
	/* Check all CA and Cert entries at once */
1932
	init_config_arr(array('ca'));
1933
	init_config_arr(array('cert'));
1934
	$all_certs = array_merge_recursive_unique($config['ca'], $config['cert']);
1935

    
1936
	foreach ($all_certs as $cert) {
1937
		/* Fetch and analyze expiration */
1938
		$expiredays = cert_get_lifetime($cert, true);
1939
		/* If the result is null, then the lifetime data is missing, so skip the invalid entry. */
1940
		if ($expiredays == null) {
1941
			continue;
1942
		}
1943
		list($lrclass, $expstring) = cert_analyze_lifetime($expiredays);
1944
		/* Only notify if the certificate is expiring soon, or has
1945
		 * already expired */
1946
		if ($lrclass != 'normal') {
1947
			$notify_string = (array_key_exists('serial', $cert)) ? gettext('Certificate Authority') : gettext('Certificate');
1948
			$notify_string .= ": {$cert['descr']} ({$cert['refid']}): {$expstring}";
1949
			$notifications[] = $notify_string;
1950
		}
1951
	}
1952
	if (!empty($notifications)) {
1953
		$message = gettext("The following CA/Certificate entries are expiring:") . "\n" .
1954
			implode("\n", $notifications);
1955
		file_notice("Certificate Expiration", $message, "Certificate Manager");
1956
	}
1957
}
1958

    
1959
?>
(7-7/60)