Project

General

Profile

Download (29 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") {
203

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

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

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

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

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

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

    
240
	return true;
241
}
242

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

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

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

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

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

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

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

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

    
294
	return true;
295
}
296

    
297
function cert_import(& $cert, $crt_str, $key_str) {
298

    
299
	$cert['crt'] = base64_encode($crt_str);
300
	$cert['prv'] = base64_encode($key_str);
301

    
302
	$subject = cert_get_subject($crt_str, false);
303
	$issuer = cert_get_issuer($crt_str, false);
304

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

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

    
317
	$cert['type'] = $type;
318

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

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

    
339
	$cert_type = cert_type_config_section($type);
340

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

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

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

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

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

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

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

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

    
394
	return true;
395
}
396

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

    
399
	$cert_type = cert_type_config_section($type);
400

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

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

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

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

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

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

    
439
	return true;
440
}
441

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

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

    
458
	$cert_type = cert_type_config_section($type);
459

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

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

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

    
475
	return $n509;
476
}
477

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

    
485
function csr_get_subject($str_crt, $decode = true) {
486

    
487
	if ($decode) {
488
		$str_crt = base64_decode($str_crt);
489
	}
490

    
491
	$components = openssl_csr_get_subject($str_crt);
492

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

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

    
506
	return $subject;
507
}
508

    
509
function cert_get_subject($str_crt, $decode = true) {
510

    
511
	if ($decode) {
512
		$str_crt = base64_decode($str_crt);
513
	}
514

    
515
	$inf_crt = openssl_x509_parse($str_crt);
516
	$components = $inf_crt['subject'];
517

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

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

    
536
	return $subject;
537
}
538

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

    
544
	if (!is_array($components)) {
545
		return;
546
	}
547

    
548
	$subject_array = array();
549

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

    
554
	return $subject_array;
555
}
556

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

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

    
575
function cert_get_issuer($str_crt, $decode = true) {
576

    
577
	if ($decode) {
578
		$str_crt = base64_decode($str_crt);
579
	}
580

    
581
	$inf_crt = openssl_x509_parse($str_crt);
582
	$components = $inf_crt['issuer'];
583

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

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

    
602
	return $issuer;
603
}
604

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

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

    
664
	return $purpose;
665
}
666

    
667
function cert_get_ocspstaple($str_crt, $decode = true) {
668
	if ($decode) {
669
		$str_crt = base64_decode($str_crt);
670
	}
671
	$crt_details = openssl_x509_parse($str_crt);
672
	if (($crt_details['extensions']['tlsfeature'] == "status_request") ||
673
	    !empty($crt_details['extensions']['1.3.6.1.5.5.7.1.24'])) {
674
		return true;
675
	}
676
	return false;
677
}
678

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

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

    
715
function cert_get_sigtype($str_crt, $decode = true) {
716
	if ($decode) {
717
		$str_crt = base64_decode($str_crt);
718
	}
719
	$crt_details = openssl_x509_parse($str_crt);
720

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

    
732
	return $signature;
733
}
734

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

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

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

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

    
787
function ca_in_use($caref) {
788
	return (is_openvpn_server_ca($caref) ||
789
		is_openvpn_client_ca($caref) ||
790
		is_ipsec_peer_ca($caref) ||
791
		is_ldap_peer_ca($caref));
792
}
793

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

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

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

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

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

    
859
function is_package_cert($certref) {
860
	$pluginparams = array();
861
	$pluginparams['type'] = 'certificates';
862
	$pluginparams['event'] = 'used_certificates';
863

    
864
	$certificates_used_by_packages = pkg_call_plugins('plugin_certificates', $pluginparams);
865

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

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

    
888
function cert_in_use($certref) {
889

    
890
	return (is_webgui_cert($certref) ||
891
		is_user_cert($certref) ||
892
		is_openvpn_server_cert($certref) ||
893
		is_openvpn_client_cert($certref) ||
894
		is_ipsec_cert($certref) ||
895
		is_captiveportal_cert($certref) ||
896
		is_package_cert($certref));
897
}
898

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

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

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

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

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

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

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

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

    
1006
	return $crl['text'];
1007
}
1008

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

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

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

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

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

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

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

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

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

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

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

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

    
1191
?>
(7-7/59)