Project

General

Profile

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

    
23
define("OPEN_SSL_CONF_PATH", "/etc/ssl/openssl.cnf");
24

    
25
require_once("functions.inc");
26

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

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

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

    
52

    
53
function & lookup_ca($refid) {
54
	global $config;
55

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

    
64
	return false;
65
}
66

    
67
function & lookup_ca_by_subject($subject) {
68
	global $config;
69

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

    
79
	return false;
80
}
81

    
82
function & lookup_cert($refid) {
83
	global $config;
84

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

    
93
	return false;
94
}
95

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

    
107
function & lookup_crl($refid) {
108
	global $config;
109

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

    
118
	return false;
119
}
120

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

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

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

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

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

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

    
200
function ca_create(& $ca, $keylen, $lifetime, $dn, $digest_alg = "sha256") {
201

    
202
	$args = array(
203
		"x509_extensions" => "v3_ca",
204
		"digest_alg" => $digest_alg,
205
		"private_key_bits" => (int)$keylen,
206
		"private_key_type" => OPENSSL_KEYTYPE_RSA,
207
		"encrypt_key" => false);
208

    
209
	// generate a new key pair
210
	$res_key = openssl_pkey_new($args);
211
	if (!$res_key) {
212
		return false;
213
	}
214

    
215
	// generate a certificate signing request
216
	$res_csr = openssl_csr_new($dn, $res_key, $args);
217
	if (!$res_csr) {
218
		return false;
219
	}
220

    
221
	// self sign the certificate
222
	$res_crt = openssl_csr_sign($res_csr, null, $res_key, $lifetime, $args);
223
	if (!$res_crt) {
224
		return false;
225
	}
226

    
227
	// export our certificate data
228
	if (!openssl_pkey_export($res_key, $str_key) ||
229
	    !openssl_x509_export($res_crt, $str_crt)) {
230
		return false;
231
	}
232

    
233
	// return our ca information
234
	$ca['crt'] = base64_encode($str_crt);
235
	$ca['prv'] = base64_encode($str_key);
236
	$ca['serial'] = 0;
237

    
238
	return true;
239
}
240

    
241
function ca_inter_create(& $ca, $keylen, $lifetime, $dn, $caref, $digest_alg = "sha256") {
242
	// Create Intermediate Certificate Authority
243
	$signing_ca =& lookup_ca($caref);
244
	if (!$signing_ca) {
245
		return false;
246
	}
247

    
248
	$signing_ca_res_crt = openssl_x509_read(base64_decode($signing_ca['crt']));
249
	$signing_ca_res_key = openssl_pkey_get_private(array(0 => base64_decode($signing_ca['prv']) , 1 => ""));
250
	if (!$signing_ca_res_crt || !$signing_ca_res_key) {
251
		return false;
252
	}
253
	$signing_ca_serial = ++$signing_ca['serial'];
254

    
255
	$args = array(
256
		"x509_extensions" => "v3_ca",
257
		"digest_alg" => $digest_alg,
258
		"private_key_bits" => (int)$keylen,
259
		"private_key_type" => OPENSSL_KEYTYPE_RSA,
260
		"encrypt_key" => false);
261

    
262
	// generate a new key pair
263
	$res_key = openssl_pkey_new($args);
264
	if (!$res_key) {
265
		return false;
266
	}
267

    
268
	// generate a certificate signing request
269
	$res_csr = openssl_csr_new($dn, $res_key, $args);
270
	if (!$res_csr) {
271
		return false;
272
	}
273

    
274
	// Sign the certificate
275
	$res_crt = openssl_csr_sign($res_csr, $signing_ca_res_crt, $signing_ca_res_key, $lifetime, $args, $signing_ca_serial);
276
	if (!$res_crt) {
277
		return false;
278
	}
279

    
280
	// export our certificate data
281
	if (!openssl_pkey_export($res_key, $str_key) ||
282
	    !openssl_x509_export($res_crt, $str_crt)) {
283
		return false;
284
	}
285

    
286
	// return our ca information
287
	$ca['crt'] = base64_encode($str_crt);
288
	$ca['prv'] = base64_encode($str_key);
289
	$ca['serial'] = 0;
290
	$ca['caref'] = $caref;
291

    
292
	return true;
293
}
294

    
295
function cert_import(& $cert, $crt_str, $key_str) {
296

    
297
	$cert['crt'] = base64_encode($crt_str);
298
	$cert['prv'] = base64_encode($key_str);
299

    
300
	$subject = cert_get_subject($crt_str, false);
301
	$issuer = cert_get_issuer($crt_str, false);
302

    
303
	// Find my issuer unless self-signed
304
	if ($issuer <> $subject) {
305
		$issuer_crt =& lookup_ca_by_subject($issuer);
306
		if ($issuer_crt) {
307
			$cert['caref'] = $issuer_crt['refid'];
308
		}
309
	}
310
	return true;
311
}
312

    
313
function cert_create(& $cert, $caref, $keylen, $lifetime, $dn, $type = "user", $digest_alg = "sha256") {
314

    
315
	$cert['type'] = $type;
316

    
317
	if ($type != "self-signed") {
318
		$cert['caref'] = $caref;
319
		$ca =& lookup_ca($caref);
320
		if (!$ca) {
321
			return false;
322
		}
323

    
324
		$ca_str_crt = base64_decode($ca['crt']);
325
		$ca_str_key = base64_decode($ca['prv']);
326
		$ca_res_crt = openssl_x509_read($ca_str_crt);
327
		$ca_res_key = openssl_pkey_get_private(array(0 => $ca_str_key, 1 => ""));
328
		if (!$ca_res_key) {
329
			return false;
330
		}
331
		if (empty($ca['serial'])) {
332
			$ca['serial'] = 0;
333
		}
334
		$ca_serial = ++$ca['serial'];
335
	}
336

    
337
	$cert_type = cert_type_config_section($type);
338

    
339
	// in case of using Subject Alternative Names use other sections (with postfix '_san')
340
	// pass subjectAltName over environment variable 'SAN'
341
	if ($dn['subjectAltName']) {
342
		putenv("SAN={$dn['subjectAltName']}"); // subjectAltName can be set _only_ via configuration file
343
		$cert_type .= '_san';
344
		unset($dn['subjectAltName']);
345
	}
346

    
347
	$args = array(
348
		"x509_extensions" => $cert_type,
349
		"digest_alg" => $digest_alg,
350
		"private_key_bits" => (int)$keylen,
351
		"private_key_type" => OPENSSL_KEYTYPE_RSA,
352
		"encrypt_key" => false);
353

    
354
	// generate a new key pair
355
	$res_key = openssl_pkey_new($args);
356
	if (!$res_key) {
357
		return false;
358
	}
359

    
360
	// If this is a self-signed cert, blank out the CA and sign with the cert's key
361
	if ($type == "self-signed") {
362
		$ca           = null;
363
		$ca_res_crt   = null;
364
		$ca_res_key   = $res_key;
365
		$ca_serial    = 0;
366
		$cert['type'] = "server";
367
	}
368

    
369
	// generate a certificate signing request
370
	$res_csr = openssl_csr_new($dn, $res_key, $args);
371
	if (!$res_csr) {
372
		return false;
373
	}
374

    
375
	// sign the certificate using an internal CA
376
	$res_crt = openssl_csr_sign($res_csr, $ca_res_crt, $ca_res_key, $lifetime,
377
				 $args, $ca_serial);
378
	if (!$res_crt) {
379
		return false;
380
	}
381

    
382
	// export our certificate data
383
	if (!openssl_pkey_export($res_key, $str_key) ||
384
	    !openssl_x509_export($res_crt, $str_crt)) {
385
		return false;
386
	}
387

    
388
	// return our certificate information
389
	$cert['crt'] = base64_encode($str_crt);
390
	$cert['prv'] = base64_encode($str_key);
391

    
392
	return true;
393
}
394

    
395
function csr_generate(& $cert, $keylen, $dn, $type = "user", $digest_alg = "sha256") {
396

    
397
	$cert_type = cert_type_config_section($type);
398

    
399
	// in case of using Subject Alternative Names use other sections (with postfix '_san')
400
	// pass subjectAltName over environment variable 'SAN'
401
	if ($dn['subjectAltName']) {
402
		putenv("SAN={$dn['subjectAltName']}"); // subjectAltName can be set _only_ via configuration file
403
		$cert_type .= '_san';
404
		unset($dn['subjectAltName']);
405
	}
406

    
407
	$args = array(
408
		"x509_extensions" => $cert_type,
409
		"req_extensions" => "req_{$cert_type}",
410
		"digest_alg" => $digest_alg,
411
		"private_key_bits" => (int)$keylen,
412
		"private_key_type" => OPENSSL_KEYTYPE_RSA,
413
		"encrypt_key" => false);
414

    
415
	// generate a new key pair
416
	$res_key = openssl_pkey_new($args);
417
	if (!$res_key) {
418
		return false;
419
	}
420

    
421
	// generate a certificate signing request
422
	$res_csr = openssl_csr_new($dn, $res_key, $args);
423
	if (!$res_csr) {
424
		return false;
425
	}
426

    
427
	// export our request data
428
	if (!openssl_pkey_export($res_key, $str_key) ||
429
	    !openssl_csr_export($res_csr, $str_csr)) {
430
		return false;
431
	}
432

    
433
	// return our request information
434
	$cert['csr'] = base64_encode($str_csr);
435
	$cert['prv'] = base64_encode($str_key);
436

    
437
	return true;
438
}
439

    
440
function csr_sign($csr, & $ca, $duration, $type = "user", $altnames, $digest_alg = "sha256") {
441
	global $config;
442
	$old_err_level = error_reporting(0);
443

    
444
	// Gather the information required for signed cert
445
	$ca_str_crt = base64_decode($ca['crt']);
446
	$ca_str_key = base64_decode($ca['prv']);
447
	$ca_res_key = openssl_pkey_get_private(array(0 => $ca_str_key, 1 => ""));
448
	if (!$ca_res_key) {
449
		return false;
450
	}
451
	if (empty($ca['serial'])) {
452
		$ca['serial'] = 0;
453
	}
454
	$ca_serial = ++$ca['serial'];
455

    
456
	$cert_type = cert_type_config_section($type);
457

    
458
	if (!empty($altnames)) {
459
		putenv("SAN={$altnames}"); // subjectAltName can be set _only_ via configuration file
460
		$cert_type .= '_san';
461
	}
462

    
463
	$args = array(
464
		"x509_extensions" => $cert_type,
465
		"digest_alg" => $digest_alg,
466
		"req_extensions" => "req_{$cert_type}"
467
	);
468

    
469
	// Sign the new cert and export it in x509 format
470
	openssl_x509_export(openssl_csr_sign($csr, $ca_str_crt, $ca_str_key, $duration, $args, $ca_serial), $n509);
471
	error_reporting($old_err_level);
472

    
473
	return $n509;
474
}
475

    
476
function csr_complete(& $cert, $str_crt) {
477
	$str_key = base64_decode($cert['prv']);
478
	cert_import($cert, $str_crt, $str_key);
479
	unset($cert['csr']);
480
	return true;
481
}
482

    
483
function csr_get_subject($str_crt, $decode = true) {
484

    
485
	if ($decode) {
486
		$str_crt = base64_decode($str_crt);
487
	}
488

    
489
	$components = openssl_csr_get_subject($str_crt);
490

    
491
	if (empty($components) || !is_array($components)) {
492
		return "unknown";
493
	}
494

    
495
	ksort($components);
496
	foreach ($components as $a => $v) {
497
		if (!strlen($subject)) {
498
			$subject = "{$a}={$v}";
499
		} else {
500
			$subject = "{$a}={$v}, {$subject}";
501
		}
502
	}
503

    
504
	return $subject;
505
}
506

    
507
function cert_get_subject($str_crt, $decode = true) {
508

    
509
	if ($decode) {
510
		$str_crt = base64_decode($str_crt);
511
	}
512

    
513
	$inf_crt = openssl_x509_parse($str_crt);
514
	$components = $inf_crt['subject'];
515

    
516
	if (empty($components) || !is_array($components)) {
517
		return "unknown";
518
	}
519

    
520
	ksort($components);
521
	foreach ($components as $a => $v) {
522
		if (is_array($v)) {
523
			ksort($v);
524
			foreach ($v as $w) {
525
				$asubject = "{$a}={$w}";
526
				$subject = (strlen($subject)) ? "{$asubject}, {$subject}" : $asubject;
527
			}
528
		} else {
529
			$asubject = "{$a}={$v}";
530
			$subject = (strlen($subject)) ? "{$asubject}, {$subject}" : $asubject;
531
		}
532
	}
533

    
534
	return $subject;
535
}
536

    
537
function cert_get_subject_array($crt) {
538
	$str_crt = base64_decode($crt);
539
	$inf_crt = openssl_x509_parse($str_crt);
540
	$components = $inf_crt['subject'];
541

    
542
	if (!is_array($components)) {
543
		return;
544
	}
545

    
546
	$subject_array = array();
547

    
548
	foreach ($components as $a => $v) {
549
		$subject_array[] = array('a' => $a, 'v' => $v);
550
	}
551

    
552
	return $subject_array;
553
}
554

    
555
function cert_get_subject_hash($crt) {
556
	$str_crt = base64_decode($crt);
557
	$inf_crt = openssl_x509_parse($str_crt);
558
	return $inf_crt['subject'];
559
}
560

    
561
function cert_get_sans($str_crt, $decode = true) {
562
	if ($decode) {
563
		$str_crt = base64_decode($str_crt);
564
	}
565
	$sans = array();
566
	$crt_details = openssl_x509_parse($str_crt);
567
	if (!empty($crt_details['extensions']['subjectAltName'])) {
568
		$sans = explode(',', $crt_details['extensions']['subjectAltName']);
569
	}
570
	return $sans;
571
}
572

    
573
function cert_get_issuer($str_crt, $decode = true) {
574

    
575
	if ($decode) {
576
		$str_crt = base64_decode($str_crt);
577
	}
578

    
579
	$inf_crt = openssl_x509_parse($str_crt);
580
	$components = $inf_crt['issuer'];
581

    
582
	if (empty($components) || !is_array($components)) {
583
		return "unknown";
584
	}
585

    
586
	ksort($components);
587
	foreach ($components as $a => $v) {
588
		if (is_array($v)) {
589
			ksort($v);
590
			foreach ($v as $w) {
591
				$aissuer = "{$a}={$w}";
592
				$issuer = (strlen($issuer)) ? "{$aissuer}, {$issuer}" : $aissuer;
593
			}
594
		} else {
595
			$aissuer = "{$a}={$v}";
596
			$issuer = (strlen($issuer)) ? "{$aissuer}, {$issuer}" : $aissuer;
597
		}
598
	}
599

    
600
	return $issuer;
601
}
602

    
603
/* Works for both RSA and ECC (crt) and key (prv) */
604
function cert_get_publickey($str_crt, $decode = true, $type = "crt") {
605
	if ($decode) {
606
		$str_crt = base64_decode($str_crt);
607
	}
608
	$certfn = tempnam('/tmp', 'CGPK');
609
	file_put_contents($certfn, $str_crt);
610
	switch ($type) {
611
		case 'prv':
612
			exec("/usr/bin/openssl pkey -in {$certfn} -pubout", $out);
613
			break;
614
		case 'crt':
615
			exec("/usr/bin/openssl x509 -in {$certfn} -inform pem -noout -pubkey", $out);
616
			break;
617
		case 'csr':
618
			exec("/usr/bin/openssl req -in {$certfn} -inform pem -noout -pubkey", $out);
619
			break;
620
		default:
621
			$out = array();
622
			break;
623
	}
624
	unlink($certfn);
625
	return implode("\n", $out);
626
}
627

    
628
function cert_get_purpose($str_crt, $decode = true) {
629
	$extended_oids = array(
630
		"1.3.6.1.5.5.8.2.2" => "IP Security IKE Intermediate",
631
	);
632
	if ($decode) {
633
		$str_crt = base64_decode($str_crt);
634
	}
635
	$crt_details = openssl_x509_parse($str_crt);
636
	$purpose = array();
637
	if (!empty($crt_details['extensions']['keyUsage'])) {
638
		$purpose['ku'] = explode(',', $crt_details['extensions']['keyUsage']);
639
		foreach ($purpose['ku'] as & $ku) {
640
			$ku = trim($ku);
641
			if (array_key_exists($ku, $extended_oids)) {
642
				$ku = $extended_oids[$ku];
643
			}
644
		}
645
	} else {
646
		$purpose['ku'] = array();
647
	}
648
	if (!empty($crt_details['extensions']['extendedKeyUsage'])) {
649
		$purpose['eku'] = explode(',', $crt_details['extensions']['extendedKeyUsage']);
650
		foreach ($purpose['eku'] as & $eku) {
651
			$eku = trim($eku);
652
			if (array_key_exists($eku, $extended_oids)) {
653
				$eku = $extended_oids[$eku];
654
			}
655
		}
656
	} else {
657
		$purpose['eku'] = array();
658
	}
659
	$purpose['ca'] = (stristr($crt_details['extensions']['basicConstraints'], 'CA:TRUE') === false) ? 'No': 'Yes';
660
	$purpose['server'] = (in_array('TLS Web Server Authentication', $purpose['eku'])) ? 'Yes': 'No';
661

    
662
	return $purpose;
663
}
664

    
665
function cert_get_ocspstaple($str_crt, $decode = true) {
666
	if ($decode) {
667
		$str_crt = base64_decode($str_crt);
668
	}
669
	$crt_details = openssl_x509_parse($str_crt);
670
	if (!empty($crt_details['extensions']['1.3.6.1.5.5.7.1.24'])) {
671
		return true;
672
	}
673
	return false;
674
}
675

    
676
function cert_get_dates($str_crt, $decode = true) {
677
	if ($decode) {
678
		$str_crt = base64_decode($str_crt);
679
	}
680
	$crt_details = openssl_x509_parse($str_crt);
681
	if ($crt_details['validFrom_time_t'] > 0) {
682
		$start = date('r', $crt_details['validFrom_time_t']);
683
	} else {
684
		$dt = DateTime::createFromFormat('ymdHis', rtrim($crt_details['validFrom'], 'Z'));
685
		if ($dt !== false) {
686
			$start = $dt->format(DateTimeInterface::RFC2822);
687
		}
688
	}
689
	if ($crt_details['validTo_time_t'] > 0) {
690
		$end = date('r', $crt_details['validTo_time_t']);
691
	} else {
692
		$dt = DateTime::createFromFormat('ymdHis', rtrim($crt_details['validTo'], 'Z'));
693
		if ($dt !== false) {
694
			$end = $dt->format(DateTimeInterface::RFC2822);
695
		}
696
	}
697
	return array($start, $end);
698
}
699

    
700
function cert_get_serial($str_crt, $decode = true) {
701
	if ($decode) {
702
		$str_crt = base64_decode($str_crt);
703
	}
704
	$crt_details = openssl_x509_parse($str_crt);
705
	if (isset($crt_details['serialNumber']) && !empty($crt_details['serialNumber'])) {
706
		return $crt_details['serialNumber'];
707
	} else {
708
		return NULL;
709
	}
710
}
711

    
712
function cert_get_sigtype($str_crt, $decode = true) {
713
	if ($decode) {
714
		$str_crt = base64_decode($str_crt);
715
	}
716
	$crt_details = openssl_x509_parse($str_crt);
717

    
718
	$signature = array();
719
	if (isset($crt_details['signatureTypeSN']) && !empty($crt_details['signatureTypeSN'])) {
720
		$signature['shortname'] = $crt_details['signatureTypeSN'];
721
	}
722
	if (isset($crt_details['signatureTypeLN']) && !empty($crt_details['signatureTypeLN'])) {
723
		$signature['longname'] = $crt_details['signatureTypeLN'];
724
	}
725
	if (isset($crt_details['signatureTypeNID']) && !empty($crt_details['signatureTypeNID'])) {
726
		$signature['nid'] = $crt_details['signatureTypeNID'];
727
	}
728

    
729
	return $signature;
730
}
731

    
732
function is_openvpn_server_ca($caref) {
733
	global $config;
734
	if (!is_array($config['openvpn']['openvpn-server'])) {
735
		return;
736
	}
737
	foreach ($config['openvpn']['openvpn-server'] as $ovpns) {
738
		if ($ovpns['caref'] == $caref) {
739
			return true;
740
		}
741
	}
742
	return false;
743
}
744

    
745
function is_openvpn_client_ca($caref) {
746
	global $config;
747
	if (!is_array($config['openvpn']['openvpn-client'])) {
748
		return;
749
	}
750
	foreach ($config['openvpn']['openvpn-client'] as $ovpnc) {
751
		if ($ovpnc['caref'] == $caref) {
752
			return true;
753
		}
754
	}
755
	return false;
756
}
757

    
758
function is_ipsec_peer_ca($caref) {
759
	global $config;
760
	if (!is_array($config['ipsec']['phase1'])) {
761
		return;
762
	}
763
	foreach ($config['ipsec']['phase1'] as $ipsec) {
764
		if ($ipsec['caref'] == $caref) {
765
			return true;
766
		}
767
	}
768
	return false;
769
}
770

    
771
function is_ldap_peer_ca($caref) {
772
	global $config;
773
	if (!is_array($config['system']['authserver'])) {
774
		return;
775
	}
776
	foreach ($config['system']['authserver'] as $authserver) {
777
		if ($authserver['ldap_caref'] == $caref) {
778
			return true;
779
		}
780
	}
781
	return false;
782
}
783

    
784
function ca_in_use($caref) {
785
	return (is_openvpn_server_ca($caref) ||
786
		is_openvpn_client_ca($caref) ||
787
		is_ipsec_peer_ca($caref) ||
788
		is_ldap_peer_ca($caref));
789
}
790

    
791
function is_user_cert($certref) {
792
	global $config;
793
	if (!is_array($config['system']['user'])) {
794
		return;
795
	}
796
	foreach ($config['system']['user'] as $user) {
797
		if (!is_array($user['cert'])) {
798
			continue;
799
		}
800
		foreach ($user['cert'] as $cert) {
801
			if ($certref == $cert) {
802
				return true;
803
			}
804
		}
805
	}
806
	return false;
807
}
808

    
809
function is_openvpn_server_cert($certref) {
810
	global $config;
811
	if (!is_array($config['openvpn']['openvpn-server'])) {
812
		return;
813
	}
814
	foreach ($config['openvpn']['openvpn-server'] as $ovpns) {
815
		if ($ovpns['certref'] == $certref) {
816
			return true;
817
		}
818
	}
819
	return false;
820
}
821

    
822
function is_openvpn_client_cert($certref) {
823
	global $config;
824
	if (!is_array($config['openvpn']['openvpn-client'])) {
825
		return;
826
	}
827
	foreach ($config['openvpn']['openvpn-client'] as $ovpnc) {
828
		if ($ovpnc['certref'] == $certref) {
829
			return true;
830
		}
831
	}
832
	return false;
833
}
834

    
835
function is_ipsec_cert($certref) {
836
	global $config;
837
	if (!is_array($config['ipsec']['phase1'])) {
838
		return;
839
	}
840
	foreach ($config['ipsec']['phase1'] as $ipsec) {
841
		if ($ipsec['certref'] == $certref) {
842
			return true;
843
		}
844
	}
845
	return false;
846
}
847

    
848
function is_webgui_cert($certref) {
849
	global $config;
850
	if (($config['system']['webgui']['ssl-certref'] == $certref) &&
851
	    ($config['system']['webgui']['protocol'] != "http")) {
852
		return true;
853
	}
854
}
855

    
856
function is_package_cert($certref) {
857
	$pluginparams = array();
858
	$pluginparams['type'] = 'certificates';
859
	$pluginparams['event'] = 'used_certificates';
860

    
861
	$certificates_used_by_packages = pkg_call_plugins('plugin_certificates', $pluginparams);
862

    
863
	/* Check if any package is using certificate */
864
	foreach ($certificates_used_by_packages as $name => $package) {
865
		if (is_array($package['certificatelist'][$certref]) &&
866
		    isset($package['certificatelist'][$certref]) > 0) {
867
			return true;
868
		}
869
	}
870
}
871

    
872
function is_captiveportal_cert($certref) {
873
	global $config;
874
	if (!is_array($config['captiveportal'])) {
875
		return;
876
	}
877
	foreach ($config['captiveportal'] as $portal) {
878
		if (isset($portal['enable']) && isset($portal['httpslogin']) && ($portal['certref'] == $certref)) {
879
			return true;
880
		}
881
	}
882
	return false;
883
}
884

    
885
function cert_in_use($certref) {
886

    
887
	return (is_webgui_cert($certref) ||
888
		is_user_cert($certref) ||
889
		is_openvpn_server_cert($certref) ||
890
		is_openvpn_client_cert($certref) ||
891
		is_ipsec_cert($certref) ||
892
		is_captiveportal_cert($certref) ||
893
		is_package_cert($certref));
894
}
895

    
896
function cert_usedby_description($refid, $certificates_used_by_packages) {
897
	$result = "";
898
	if (is_array($certificates_used_by_packages)) {
899
		foreach ($certificates_used_by_packages as $name => $package) {
900
			if (isset($package['certificatelist'][$refid])) {
901
				$hint = "" ;
902
				if (is_array($package['certificatelist'][$refid])) {
903
					foreach ($package['certificatelist'][$refid] as $cert_used) {
904
						$hint = $hint . $cert_used['usedby']."\n";
905
					}
906
				}
907
				$count = count($package['certificatelist'][$refid]);
908
				$result .= "<div title='".htmlspecialchars($hint)."'>";
909
				$result .= htmlspecialchars($package['pkgname'])." ($count)<br />";
910
				$result .= "</div>";
911
			}
912
		}
913
	}
914
	return $result;
915
}
916

    
917
/* Detect a rollover at 2038 on some platforms (e.g. ARM)
918
 * See: https://redmine.pfsense.org/issues/9098 */
919
function crl_get_max_lifetime($max = 9999) {
920
	if ($max <= 0) {
921
		return 0;
922
	}
923
	$current_time = time();
924
	while ((int)($current_time + ($max * 24 * 60 * 60)) < 0) {
925
		$max--;
926
	}
927
	return $max;
928
}
929

    
930
function crl_create(& $crl, $caref, $name, $serial = 0, $lifetime = 3650) {
931
	global $config;
932
	$max_lifetime = crl_get_max_lifetime();
933
	$ca =& lookup_ca($caref);
934
	if (!$ca) {
935
		return false;
936
	}
937
	$crl['descr'] = $name;
938
	$crl['caref'] = $caref;
939
	$crl['serial'] = $serial;
940
	$crl['lifetime'] = ($lifetime > $max_lifetime) ? $max_lifetime : $lifetime;
941
	$crl['cert'] = array();
942
	$config['crl'][] = $crl;
943
	return $crl;
944
}
945

    
946
function crl_update(& $crl) {
947
	require_once('ASN1.php');
948
	require_once('ASN1_UTF8STRING.php');
949
	require_once('ASN1_ASCIISTRING.php');
950
	require_once('ASN1_BITSTRING.php');
951
	require_once('ASN1_BOOL.php');
952
	require_once('ASN1_GENERALTIME.php');
953
	require_once('ASN1_INT.php');
954
	require_once('ASN1_ENUM.php');
955
	require_once('ASN1_NULL.php');
956
	require_once('ASN1_OCTETSTRING.php');
957
	require_once('ASN1_OID.php');
958
	require_once('ASN1_SEQUENCE.php');
959
	require_once('ASN1_SET.php');
960
	require_once('ASN1_SIMPLE.php');
961
	require_once('ASN1_TELETEXSTRING.php');
962
	require_once('ASN1_UTCTIME.php');
963
	require_once('OID.php');
964
	require_once('X509.php');
965
	require_once('X509_CERT.php');
966
	require_once('X509_CRL.php');
967

    
968
	global $config;
969
	$max_lifetime = crl_get_max_lifetime();
970
	$ca =& lookup_ca($crl['caref']);
971
	if (!$ca) {
972
		return false;
973
	}
974
	// If we have text but no certs, it was imported and cannot be updated.
975
	if (($crl["method"] != "internal") && (!empty($crl['text']) && empty($crl['cert']))) {
976
		return false;
977
	}
978
	$crl['serial']++;
979
	$ca_cert = \Ukrbublik\openssl_x509_crl\X509::pem2der(base64_decode($ca['crt']));
980
	$ca_pkey = openssl_pkey_get_private(base64_decode($ca['prv']));
981

    
982
	$crlconf = array(
983
		'no' => $crl['serial'],
984
		'version' => 2,
985
		'days' => ($crl['lifetime'] > $max_lifetime) ? $max_lifetime : $crl['lifetime'],
986
		'alg' => OPENSSL_ALGO_SHA1,
987
		'revoked' => array()
988
	);
989

    
990
	if (is_array($crl['cert']) && (count($crl['cert']) > 0)) {
991
		foreach ($crl['cert'] as $cert) {
992
			$crlconf['revoked'][] = array(
993
				'serial' => cert_get_serial($cert["crt"], true),
994
				'rev_date' => $cert["revoke_time"],
995
				'reason' => ($cert["reason"] == -1) ? null : (int) $cert["reason"],
996
			);
997
		}
998
	}
999

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

    
1003
	return $crl['text'];
1004
}
1005

    
1006
function cert_revoke($cert, & $crl, $reason = OCSP_REVOKED_STATUS_UNSPECIFIED) {
1007
	global $config;
1008
	if (is_cert_revoked($cert, $crl['refid'])) {
1009
		return true;
1010
	}
1011
	// If we have text but no certs, it was imported and cannot be updated.
1012
	if (!is_crl_internal($crl)) {
1013
		return false;
1014
	}
1015
	$cert["reason"] = $reason;
1016
	$cert["revoke_time"] = time();
1017
	$crl["cert"][] = $cert;
1018
	crl_update($crl);
1019
	return true;
1020
}
1021

    
1022
function cert_unrevoke($cert, & $crl) {
1023
	global $config;
1024
	if (!is_crl_internal($crl)) {
1025
		return false;
1026
	}
1027
	foreach ($crl['cert'] as $id => $rcert) {
1028
		if (($rcert['refid'] == $cert['refid']) || ($rcert['descr'] == $cert['descr'])) {
1029
			unset($crl['cert'][$id]);
1030
			if (count($crl['cert']) == 0) {
1031
				// Protect against accidentally switching the type to imported, for older CRLs
1032
				if (!isset($crl['method'])) {
1033
					$crl['method'] = "internal";
1034
				}
1035
				crl_update($crl);
1036
			} else {
1037
				crl_update($crl);
1038
			}
1039
			return true;
1040
		}
1041
	}
1042
	return false;
1043
}
1044

    
1045
/* Compare two certificates to see if they match. */
1046
function cert_compare($cert1, $cert2) {
1047
	/* Ensure two certs are identical by first checking that their issuers match, then
1048
		subjects, then serial numbers, and finally the moduli. Anything less strict
1049
		could accidentally count two similar, but different, certificates as
1050
		being identical. */
1051
	$c1 = base64_decode($cert1['crt']);
1052
	$c2 = base64_decode($cert2['crt']);
1053
	if ((cert_get_issuer($c1, false) == cert_get_issuer($c2, false)) &&
1054
	    (cert_get_subject($c1, false) == cert_get_subject($c2, false)) &&
1055
	    (cert_get_serial($c1, false) == cert_get_serial($c2, false)) &&
1056
	    (cert_get_publickey($c1, false) == cert_get_publickey($c2, false))) {
1057
		return true;
1058
	}
1059
	return false;
1060
}
1061

    
1062
function is_cert_revoked($cert, $crlref = "") {
1063
	global $config;
1064
	if (!is_array($config['crl'])) {
1065
		return false;
1066
	}
1067

    
1068
	if (!empty($crlref)) {
1069
		$crl = lookup_crl($crlref);
1070
		if (!is_array($crl['cert'])) {
1071
			return false;
1072
		}
1073
		foreach ($crl['cert'] as $rcert) {
1074
			if (cert_compare($rcert, $cert)) {
1075
				return true;
1076
			}
1077
		}
1078
	} else {
1079
		foreach ($config['crl'] as $crl) {
1080
			if (!is_array($crl['cert'])) {
1081
				continue;
1082
			}
1083
			foreach ($crl['cert'] as $rcert) {
1084
				if (cert_compare($rcert, $cert)) {
1085
					return true;
1086
				}
1087
			}
1088
		}
1089
	}
1090
	return false;
1091
}
1092

    
1093
function is_openvpn_server_crl($crlref) {
1094
	global $config;
1095
	if (!is_array($config['openvpn']['openvpn-server'])) {
1096
		return;
1097
	}
1098
	foreach ($config['openvpn']['openvpn-server'] as $ovpns) {
1099
		if (!empty($ovpns['crlref']) && ($ovpns['crlref'] == $crlref)) {
1100
			return true;
1101
		}
1102
	}
1103
	return false;
1104
}
1105

    
1106
// Keep this general to allow for future expansion. See cert_in_use() above.
1107
function crl_in_use($crlref) {
1108
	return (is_openvpn_server_crl($crlref));
1109
}
1110

    
1111
function is_crl_internal($crl) {
1112
	return (!(!empty($crl['text']) && empty($crl['cert'])) || ($crl["method"] == "internal"));
1113
}
1114

    
1115
function cert_get_cn($crt, $isref = false) {
1116
	/* If this is a certref, not an actual cert, look up the cert first */
1117
	if ($isref) {
1118
		$cert = lookup_cert($crt);
1119
		/* If it's not a valid cert, bail. */
1120
		if (!(is_array($cert) && !empty($cert['crt']))) {
1121
			return "";
1122
		}
1123
		$cert = $cert['crt'];
1124
	} else {
1125
		$cert = $crt;
1126
	}
1127
	$sub = cert_get_subject_array($cert);
1128
	if (is_array($sub)) {
1129
		foreach ($sub as $s) {
1130
			if (strtoupper($s['a']) == "CN") {
1131
				return $s['v'];
1132
			}
1133
		}
1134
	}
1135
	return "";
1136
}
1137

    
1138
function cert_escape_x509_chars($str, $reverse = false) {
1139
	/* Characters which need escaped when present in x.509 fields.
1140
	 * See https://www.ietf.org/rfc/rfc4514.txt
1141
	 *
1142
	 * The backslash (\) must be listed first in these arrays!
1143
	 */
1144
	$cert_directory_string_special_chars = array('\\', '"', '#', '+', ',', ';', '<', '=', '>');
1145
	$cert_directory_string_special_chars_esc = array('\\\\', '\"', '\#', '\+', '\,', '\;', '\<', '\=', '\>');
1146
	if ($reverse) {
1147
		return str_replace($cert_directory_string_special_chars_esc, $cert_directory_string_special_chars, $str);
1148
	} else {
1149
		/* First unescape and then escape again, to prevent possible double escaping. */
1150
		return str_replace($cert_directory_string_special_chars, $cert_directory_string_special_chars_esc, cert_escape_x509_chars($str, true));
1151
	}
1152
}
1153

    
1154
function cert_add_altname_type($str) {
1155
	$type = "";
1156
	if (is_ipaddr($str)) {
1157
		$type = "IP";
1158
	} elseif (is_hostname($str, true)) {
1159
		$type = "DNS";
1160
	} elseif (is_URL($str)) {
1161
		$type = "URI";
1162
	} elseif (filter_var($str, FILTER_VALIDATE_EMAIL)) {
1163
		$type = "email";
1164
	}
1165
	if (!empty($type)) {
1166
		return "{$type}:" . cert_escape_x509_chars($str);
1167
	} else {
1168
		return null;
1169
	}
1170
}
1171

    
1172
function cert_type_config_section($type) {
1173
	switch ($type) {
1174
		case "ca":
1175
			$cert_type = "v3_ca";
1176
			break;
1177
		case "server":
1178
		case "self-signed":
1179
			$cert_type = "server";
1180
			break;
1181
		default:
1182
			$cert_type = "usr_cert";
1183
			break;
1184
	}
1185
	return $cert_type;
1186
}
1187

    
1188
?>
(7-7/60)