Project

General

Profile

Download (26.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-2016 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
$openssl_crl_status = array(
32
	OCSP_REVOKED_STATUS_NOSTATUS              => "No Status (default)",
33
	OCSP_REVOKED_STATUS_UNSPECIFIED           => "Unspecified",
34
	OCSP_REVOKED_STATUS_KEYCOMPROMISE         => "Key Compromise",
35
	OCSP_REVOKED_STATUS_CACOMPROMISE          => "CA Compromise",
36
	OCSP_REVOKED_STATUS_AFFILIATIONCHANGED    => "Affiliation Changed",
37
	OCSP_REVOKED_STATUS_SUPERSEDED            => "Superseded",
38
	OCSP_REVOKED_STATUS_CESSATIONOFOPERATION  => "Cessation of Operation",
39
	OCSP_REVOKED_STATUS_CERTIFICATEHOLD       => "Certificate Hold"
40
);
41

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

    
50

    
51
function & lookup_ca($refid) {
52
	global $config;
53

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

    
62
	return false;
63
}
64

    
65
function & lookup_ca_by_subject($subject) {
66
	global $config;
67

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

    
77
	return false;
78
}
79

    
80
function & lookup_cert($refid) {
81
	global $config;
82

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

    
91
	return false;
92
}
93

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

    
105
function & lookup_crl($refid) {
106
	global $config;
107

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

    
116
	return false;
117
}
118

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

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

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

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

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

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

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

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

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

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

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

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

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

    
236
	return true;
237
}
238

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

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

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

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

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

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

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

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

    
290
	return true;
291
}
292

    
293
function cert_import(& $cert, $crt_str, $key_str) {
294

    
295
	$cert['crt'] = base64_encode($crt_str);
296
	$cert['prv'] = base64_encode($key_str);
297

    
298
	$subject = cert_get_subject($crt_str, false);
299
	$issuer = cert_get_issuer($crt_str, false);
300

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

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

    
313
	$cert['type'] = $type;
314

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

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

    
335
	$cert_type = cert_type_config_section($type);
336

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

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

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

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

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

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

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

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

    
390
	return true;
391
}
392

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

    
395
	$cert_type = cert_type_config_section($type);
396

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

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

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

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

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

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

    
435
	return true;
436
}
437

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

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

    
454
	$cert_type = cert_type_config_section($type);
455

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

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

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

    
471
	return $n509;
472
}
473

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

    
481
function csr_get_subject($str_crt, $decode = true) {
482

    
483
	if ($decode) {
484
		$str_crt = base64_decode($str_crt);
485
	}
486

    
487
	$components = openssl_csr_get_subject($str_crt);
488

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

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

    
502
	return $subject;
503
}
504

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

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

    
511
	$inf_crt = openssl_x509_parse($str_crt);
512
	$components = $inf_crt['subject'];
513

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

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

    
532
	return $subject;
533
}
534

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

    
540
	if (!is_array($components)) {
541
		return;
542
	}
543

    
544
	$subject_array = array();
545

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

    
550
	return $subject_array;
551
}
552

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

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

    
571
function cert_get_issuer($str_crt, $decode = true) {
572

    
573
	if ($decode) {
574
		$str_crt = base64_decode($str_crt);
575
	}
576

    
577
	$inf_crt = openssl_x509_parse($str_crt);
578
	$components = $inf_crt['issuer'];
579

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

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

    
598
	return $issuer;
599
}
600

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

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

    
657
	return $purpose;
658
}
659

    
660
function cert_get_dates($str_crt, $decode = true) {
661
	if ($decode) {
662
		$str_crt = base64_decode($str_crt);
663
	}
664
	$crt_details = openssl_x509_parse($str_crt);
665
	if ($crt_details['validFrom_time_t'] > 0) {
666
		$start = date('r', $crt_details['validFrom_time_t']);
667
	}
668
	if ($crt_details['validTo_time_t'] > 0) {
669
		$end = date('r', $crt_details['validTo_time_t']);
670
	}
671
	return array($start, $end);
672
}
673

    
674
function cert_get_serial($str_crt, $decode = true) {
675
	if ($decode) {
676
		$str_crt = base64_decode($str_crt);
677
	}
678
	$crt_details = openssl_x509_parse($str_crt);
679
	if (isset($crt_details['serialNumber']) && !empty($crt_details['serialNumber'])) {
680
		return $crt_details['serialNumber'];
681
	} else {
682
		return NULL;
683
	}
684
}
685

    
686
function cert_get_sigtype($str_crt, $decode = true) {
687
	if ($decode) {
688
		$str_crt = base64_decode($str_crt);
689
	}
690
	$crt_details = openssl_x509_parse($str_crt);
691

    
692
	$signature = array();
693
	if (isset($crt_details['signatureTypeSN']) && !empty($crt_details['signatureTypeSN'])) {
694
		$signature['shortname'] = $crt_details['signatureTypeSN'];
695
	}
696
	if (isset($crt_details['signatureTypeLN']) && !empty($crt_details['signatureTypeLN'])) {
697
		$signature['longname'] = $crt_details['signatureTypeLN'];
698
	}
699
	if (isset($crt_details['signatureTypeNID']) && !empty($crt_details['signatureTypeNID'])) {
700
		$signature['nid'] = $crt_details['signatureTypeNID'];
701
	}
702

    
703
	return $signature;
704
}
705

    
706
function is_openvpn_server_ca($caref) {
707
	global $config;
708
	if (!is_array($config['openvpn']['openvpn-server'])) {
709
		return;
710
	}
711
	foreach ($config['openvpn']['openvpn-server'] as $ovpns) {
712
		if ($ovpns['caref'] == $caref) {
713
			return true;
714
		}
715
	}
716
	return false;
717
}
718

    
719
function is_openvpn_client_ca($caref) {
720
	global $config;
721
	if (!is_array($config['openvpn']['openvpn-client'])) {
722
		return;
723
	}
724
	foreach ($config['openvpn']['openvpn-client'] as $ovpnc) {
725
		if ($ovpnc['caref'] == $caref) {
726
			return true;
727
		}
728
	}
729
	return false;
730
}
731

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

    
745
function is_ldap_peer_ca($caref) {
746
	global $config;
747
	if (!is_array($config['system']['authserver'])) {
748
		return;
749
	}
750
	foreach ($config['system']['authserver'] as $authserver) {
751
		if ($authserver['ldap_caref'] == $caref) {
752
			return true;
753
		}
754
	}
755
	return false;
756
}
757

    
758
function ca_in_use($caref) {
759
	return (is_openvpn_server_ca($caref) ||
760
		is_openvpn_client_ca($caref) ||
761
		is_ipsec_peer_ca($caref) ||
762
		is_ldap_peer_ca($caref));
763
}
764

    
765
function is_user_cert($certref) {
766
	global $config;
767
	if (!is_array($config['system']['user'])) {
768
		return;
769
	}
770
	foreach ($config['system']['user'] as $user) {
771
		if (!is_array($user['cert'])) {
772
			continue;
773
		}
774
		foreach ($user['cert'] as $cert) {
775
			if ($certref == $cert) {
776
				return true;
777
			}
778
		}
779
	}
780
	return false;
781
}
782

    
783
function is_openvpn_server_cert($certref) {
784
	global $config;
785
	if (!is_array($config['openvpn']['openvpn-server'])) {
786
		return;
787
	}
788
	foreach ($config['openvpn']['openvpn-server'] as $ovpns) {
789
		if ($ovpns['certref'] == $certref) {
790
			return true;
791
		}
792
	}
793
	return false;
794
}
795

    
796
function is_openvpn_client_cert($certref) {
797
	global $config;
798
	if (!is_array($config['openvpn']['openvpn-client'])) {
799
		return;
800
	}
801
	foreach ($config['openvpn']['openvpn-client'] as $ovpnc) {
802
		if ($ovpnc['certref'] == $certref) {
803
			return true;
804
		}
805
	}
806
	return false;
807
}
808

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

    
822
function is_webgui_cert($certref) {
823
	global $config;
824
	if (($config['system']['webgui']['ssl-certref'] == $certref) &&
825
	    ($config['system']['webgui']['protocol'] != "http")) {
826
		return true;
827
	}
828
}
829

    
830
function is_package_cert($certref) {
831
	$pluginparams = array();
832
	$pluginparams['type'] = 'certificates';
833
	$pluginparams['event'] = 'used_certificates';
834

    
835
	$certificates_used_by_packages = pkg_call_plugins('plugin_certificates', $pluginparams);
836

    
837
	/* Check if any package is using certificate */
838
	foreach ($certificates_used_by_packages as $name => $package) {
839
		if (is_array($package['certificatelist'][$certref]) &&
840
		    isset($package['certificatelist'][$certref]) > 0) {
841
			return true;
842
		}
843
	}
844
}
845

    
846
function is_captiveportal_cert($certref) {
847
	global $config;
848
	if (!is_array($config['captiveportal'])) {
849
		return;
850
	}
851
	foreach ($config['captiveportal'] as $portal) {
852
		if (isset($portal['enable']) && isset($portal['httpslogin']) && ($portal['certref'] == $certref)) {
853
			return true;
854
		}
855
	}
856
	return false;
857
}
858

    
859
function cert_in_use($certref) {
860

    
861
	return (is_webgui_cert($certref) ||
862
		is_user_cert($certref) ||
863
		is_openvpn_server_cert($certref) ||
864
		is_openvpn_client_cert($certref) ||
865
		is_ipsec_cert($certref) ||
866
		is_captiveportal_cert($certref) ||
867
		is_package_cert($certref));
868
}
869

    
870
function cert_usedby_description($refid, $certificates_used_by_packages) {
871
	$result = "";
872
	if (is_array($certificates_used_by_packages)) {
873
		foreach ($certificates_used_by_packages as $name => $package) {
874
			if (isset($package['certificatelist'][$refid])) {
875
				$hint = "" ;
876
				if (is_array($package['certificatelist'][$refid])) {
877
					foreach ($package['certificatelist'][$refid] as $cert_used) {
878
						$hint = $hint . $cert_used['usedby']."\n";
879
					}
880
				}
881
				$count = count($package['certificatelist'][$refid]);
882
				$result .= "<div title='".htmlspecialchars($hint)."'>";
883
				$result .= htmlspecialchars($package['pkgname'])." ($count)<br />";
884
				$result .= "</div>";
885
			}
886
		}
887
	}
888
	return $result;
889
}
890

    
891
function crl_create(& $crl, $caref, $name, $serial = 0, $lifetime = 9999) {
892
	global $config;
893
	$ca =& lookup_ca($caref);
894
	if (!$ca) {
895
		return false;
896
	}
897
	$crl['descr'] = $name;
898
	$crl['caref'] = $caref;
899
	$crl['serial'] = $serial;
900
	$crl['lifetime'] = $lifetime;
901
	$crl['cert'] = array();
902
	$crl_res = crl_update($crl);
903
	$config['crl'][] = $crl;
904
	return $crl_res;
905
}
906

    
907
function crl_update(& $crl) {
908
	global $config;
909
	$ca =& lookup_ca($crl['caref']);
910
	if (!$ca) {
911
		return false;
912
	}
913
	// If we have text but no certs, it was imported and cannot be updated.
914
	if (($crl["method"] != "internal") && (!empty($crl['text']) && empty($crl['cert']))) {
915
		return false;
916
	}
917
	$crl['serial']++;
918
	$ca_str_crt = base64_decode($ca['crt']);
919
	$ca_str_key = base64_decode($ca['prv']);
920
	$crl_res = openssl_crl_new($ca_str_crt, $crl['serial'], $crl['lifetime']);
921
	if (is_array($crl['cert']) && (count($crl['cert']) > 0)) {
922
		foreach ($crl['cert'] as $cert) {
923
			openssl_crl_revoke_cert($crl_res, base64_decode($cert["crt"]), $cert["revoke_time"], $cert["reason"]);
924
		}
925
	}
926
	openssl_crl_export($crl_res, $crl_text, $ca_str_key);
927
	$crl['text'] = base64_encode($crl_text);
928
	return $crl_res;
929
}
930

    
931
function cert_revoke($cert, & $crl, $reason = OCSP_REVOKED_STATUS_UNSPECIFIED) {
932
	global $config;
933
	if (is_cert_revoked($cert, $crl['refid'])) {
934
		return true;
935
	}
936
	// If we have text but no certs, it was imported and cannot be updated.
937
	if (!is_crl_internal($crl)) {
938
		return false;
939
	}
940
	$cert["reason"] = $reason;
941
	$cert["revoke_time"] = time();
942
	$crl["cert"][] = $cert;
943
	crl_update($crl);
944
	return true;
945
}
946

    
947
function cert_unrevoke($cert, & $crl) {
948
	global $config;
949
	if (!is_crl_internal($crl)) {
950
		return false;
951
	}
952
	foreach ($crl['cert'] as $id => $rcert) {
953
		if (($rcert['refid'] == $cert['refid']) || ($rcert['descr'] == $cert['descr'])) {
954
			unset($crl['cert'][$id]);
955
			if (count($crl['cert']) == 0) {
956
				// Protect against accidentally switching the type to imported, for older CRLs
957
				if (!isset($crl['method'])) {
958
					$crl['method'] = "internal";
959
				}
960
				crl_update($crl);
961
			} else {
962
				crl_update($crl);
963
			}
964
			return true;
965
		}
966
	}
967
	return false;
968
}
969

    
970
/* Compare two certificates to see if they match. */
971
function cert_compare($cert1, $cert2) {
972
	/* Ensure two certs are identical by first checking that their issuers match, then
973
		subjects, then serial numbers, and finally the moduli. Anything less strict
974
		could accidentally count two similar, but different, certificates as
975
		being identical. */
976
	$c1 = base64_decode($cert1['crt']);
977
	$c2 = base64_decode($cert2['crt']);
978
	if ((cert_get_issuer($c1, false) == cert_get_issuer($c2, false)) &&
979
	    (cert_get_subject($c1, false) == cert_get_subject($c2, false)) &&
980
	    (cert_get_serial($c1, false) == cert_get_serial($c2, false)) &&
981
	    (cert_get_publickey($c1, false) == cert_get_publickey($c2, false))) {
982
		return true;
983
	}
984
	return false;
985
}
986

    
987
function is_cert_revoked($cert, $crlref = "") {
988
	global $config;
989
	if (!is_array($config['crl'])) {
990
		return false;
991
	}
992

    
993
	if (!empty($crlref)) {
994
		$crl = lookup_crl($crlref);
995
		if (!is_array($crl['cert'])) {
996
			return false;
997
		}
998
		foreach ($crl['cert'] as $rcert) {
999
			if (cert_compare($rcert, $cert)) {
1000
				return true;
1001
			}
1002
		}
1003
	} else {
1004
		foreach ($config['crl'] as $crl) {
1005
			if (!is_array($crl['cert'])) {
1006
				continue;
1007
			}
1008
			foreach ($crl['cert'] as $rcert) {
1009
				if (cert_compare($rcert, $cert)) {
1010
					return true;
1011
				}
1012
			}
1013
		}
1014
	}
1015
	return false;
1016
}
1017

    
1018
function is_openvpn_server_crl($crlref) {
1019
	global $config;
1020
	if (!is_array($config['openvpn']['openvpn-server'])) {
1021
		return;
1022
	}
1023
	foreach ($config['openvpn']['openvpn-server'] as $ovpns) {
1024
		if (!empty($ovpns['crlref']) && ($ovpns['crlref'] == $crlref)) {
1025
			return true;
1026
		}
1027
	}
1028
	return false;
1029
}
1030

    
1031
// Keep this general to allow for future expansion. See cert_in_use() above.
1032
function crl_in_use($crlref) {
1033
	return (is_openvpn_server_crl($crlref));
1034
}
1035

    
1036
function is_crl_internal($crl) {
1037
	return (!(!empty($crl['text']) && empty($crl['cert'])) || ($crl["method"] == "internal"));
1038
}
1039

    
1040
function cert_get_cn($crt, $isref = false) {
1041
	/* If this is a certref, not an actual cert, look up the cert first */
1042
	if ($isref) {
1043
		$cert = lookup_cert($crt);
1044
		/* If it's not a valid cert, bail. */
1045
		if (!(is_array($cert) && !empty($cert['crt']))) {
1046
			return "";
1047
		}
1048
		$cert = $cert['crt'];
1049
	} else {
1050
		$cert = $crt;
1051
	}
1052
	$sub = cert_get_subject_array($cert);
1053
	if (is_array($sub)) {
1054
		foreach ($sub as $s) {
1055
			if (strtoupper($s['a']) == "CN") {
1056
				return $s['v'];
1057
			}
1058
		}
1059
	}
1060
	return "";
1061
}
1062

    
1063
function cert_escape_x509_chars($str, $reverse = false) {
1064
	/* Characters which need escaped when present in x.509 fields.
1065
	 * See https://www.ietf.org/rfc/rfc4514.txt
1066
	 *
1067
	 * The backslash (\) must be listed first in these arrays!
1068
	 */
1069
	$cert_directory_string_special_chars = array('\\', '"', '#', '+', ',', ';', '<', '=', '>');
1070
	$cert_directory_string_special_chars_esc = array('\\\\', '\"', '\#', '\+', '\,', '\;', '\<', '\=', '\>');
1071
	if ($reverse) {
1072
		return str_replace($cert_directory_string_special_chars_esc, $cert_directory_string_special_chars, $str);
1073
	} else {
1074
		/* First unescape and then escape again, to prevent possible double escaping. */
1075
		return str_replace($cert_directory_string_special_chars, $cert_directory_string_special_chars_esc, cert_escape_x509_chars($str, true));
1076
	}
1077
}
1078

    
1079
function cert_add_altname_type($str) {
1080
	$type = "";
1081
	if (is_ipaddr($str)) {
1082
		$type = "IP";
1083
	} elseif (is_hostname($str)) {
1084
		$type = "DNS";
1085
	} elseif (is_URL($str)) {
1086
		$type = "URI";
1087
	} elseif (filter_var($str, FILTER_VALIDATE_EMAIL)) {
1088
		$type = "email";
1089
	}
1090
	if (!empty($type)) {
1091
		return "{$type}:" . cert_escape_x509_chars($str);
1092
	} else {
1093
		return "";
1094
	}
1095
}
1096

    
1097
function cert_type_config_section($type) {
1098
	switch ($type) {
1099
		case "ca":
1100
			$cert_type = "v3_ca";
1101
			break;
1102
		case "server":
1103
		case "self-signed":
1104
			$cert_type = "server";
1105
			break;
1106
		default:
1107
			$cert_type = "usr_cert";
1108
			break;
1109
	}
1110
	return $cert_type;
1111
}
1112

    
1113
?>
(6-6/54)