Project

General

Profile

Download (17.7 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * system_camanager.php
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-2018 Rubicon Communications, LLC (Netgate)
7
 * Copyright (c) 2008 Shrew Soft Inc
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
##|+PRIV
24
##|*IDENT=page-system-camanager
25
##|*NAME=System: CA Manager
26
##|*DESCR=Allow access to the 'System: CA Manager' page.
27
##|*MATCH=system_camanager.php*
28
##|-PRIV
29

    
30
require_once("guiconfig.inc");
31
require_once("certs.inc");
32
require_once("pfsense-utils.inc");
33

    
34
$ca_methods = array(
35
	"internal" => gettext("Create an internal Certificate Authority"),
36
	"existing" => gettext("Import an existing Certificate Authority"),
37
	"intermediate" => gettext("Create an intermediate Certificate Authority"));
38

    
39
$ca_keylens = array("1024", "2048", "3072", "4096", "6144", "7680", "8192", "15360", "16384");
40
global $openssl_digest_algs;
41

    
42
if (isset($_REQUEST['id']) && is_numericint($_REQUEST['id'])) {
43
	$id = $_REQUEST['id'];
44
}
45

    
46
if (!is_array($config['ca'])) {
47
	$config['ca'] = array();
48
}
49

    
50
$a_ca =& $config['ca'];
51

    
52
if (!is_array($config['cert'])) {
53
	$config['cert'] = array();
54
}
55

    
56
$a_cert =& $config['cert'];
57

    
58
if (!is_array($config['crl'])) {
59
	$config['crl'] = array();
60
}
61

    
62
$a_crl =& $config['crl'];
63

    
64
if ($_REQUEST['act']) {
65
	$act = $_REQUEST['act'];
66
}
67

    
68
if ($_POST['act'] == "del") {
69

    
70
	if (!isset($a_ca[$id])) {
71
		pfSenseHeader("system_camanager.php");
72
		exit;
73
	}
74

    
75
	/* Only remove CA reference when deleting. It can be reconnected if a new matching CA is imported */
76
	$index = count($a_cert) - 1;
77
	for (;$index >= 0; $index--) {
78
		if ($a_cert[$index]['caref'] == $a_ca[$id]['refid']) {
79
			unset($a_cert[$index]['caref']);
80
		}
81
	}
82

    
83
	/* Remove any CRLs for this CA, there is no way to recover the connection once the CA has been removed. */
84
	$index = count($a_crl) - 1;
85
	for (;$index >= 0; $index--) {
86
		if ($a_crl[$index]['caref'] == $a_ca[$id]['refid']) {
87
			unset($a_crl[$index]);
88
		}
89
	}
90

    
91
	$name = $a_ca[$id]['descr'];
92
	unset($a_ca[$id]);
93
	write_config();
94
	$savemsg = sprintf(gettext("Certificate Authority %s and its CRLs (if any) successfully deleted."), htmlspecialchars($name));
95
	pfSenseHeader("system_camanager.php");
96
	exit;
97
}
98

    
99
if ($act == "edit") {
100
	if (!$a_ca[$id]) {
101
		pfSenseHeader("system_camanager.php");
102
		exit;
103
	}
104
	$pconfig['method'] = 'existing';
105
	$pconfig['descr']  = $a_ca[$id]['descr'];
106
	$pconfig['refid']  = $a_ca[$id]['refid'];
107
	$pconfig['cert']   = base64_decode($a_ca[$id]['crt']);
108
	$pconfig['serial'] = $a_ca[$id]['serial'];
109
	if (!empty($a_ca[$id]['prv'])) {
110
		$pconfig['key'] = base64_decode($a_ca[$id]['prv']);
111
	}
112
}
113

    
114
if ($act == "new") {
115
	$pconfig['method'] = $_POST['method'];
116
	$pconfig['keylen'] = "2048";
117
	$pconfig['digest_alg'] = "sha256";
118
	$pconfig['lifetime'] = "3650";
119
	$pconfig['dn_commonname'] = "internal-ca";
120
}
121

    
122
if ($act == "exp") {
123

    
124
	if (!$a_ca[$id]) {
125
		pfSenseHeader("system_camanager.php");
126
		exit;
127
	}
128

    
129
	$exp_name = urlencode("{$a_ca[$id]['descr']}.crt");
130
	$exp_data = base64_decode($a_ca[$id]['crt']);
131
	$exp_size = strlen($exp_data);
132

    
133
	header("Content-Type: application/octet-stream");
134
	header("Content-Disposition: attachment; filename={$exp_name}");
135
	header("Content-Length: $exp_size");
136
	echo $exp_data;
137
	exit;
138
}
139

    
140
if ($act == "expkey") {
141

    
142
	if (!$a_ca[$id]) {
143
		pfSenseHeader("system_camanager.php");
144
		exit;
145
	}
146

    
147
	$exp_name = urlencode("{$a_ca[$id]['descr']}.key");
148
	$exp_data = base64_decode($a_ca[$id]['prv']);
149
	$exp_size = strlen($exp_data);
150

    
151
	header("Content-Type: application/octet-stream");
152
	header("Content-Disposition: attachment; filename={$exp_name}");
153
	header("Content-Length: $exp_size");
154
	echo $exp_data;
155
	exit;
156
}
157

    
158
if ($_POST['save']) {
159

    
160
	unset($input_errors);
161
	$input_errors = array();
162
	$pconfig = $_POST;
163

    
164
	/* input validation */
165
	if ($pconfig['method'] == "existing") {
166
		$reqdfields = explode(" ", "descr cert");
167
		$reqdfieldsn = array(
168
			gettext("Descriptive name"),
169
			gettext("Certificate data"));
170
		if ($_POST['cert'] && (!strstr($_POST['cert'], "BEGIN CERTIFICATE") || !strstr($_POST['cert'], "END CERTIFICATE"))) {
171
			$input_errors[] = gettext("This certificate does not appear to be valid.");
172
		}
173
		if ($_POST['key'] && strstr($_POST['key'], "ENCRYPTED")) {
174
			$input_errors[] = gettext("Encrypted private keys are not yet supported.");
175
		}
176
		if (!$input_errors && !empty($_POST['key']) && cert_get_publickey($_POST['cert'], false) != cert_get_publickey($_POST['key'], false, 'prv')) {
177
			$input_errors[] = gettext("The submitted private key does not match the submitted certificate data.");
178
		}
179
		/* we must ensure the certificate is capable of acting as a CA
180
		 * https://redmine.pfsense.org/issues/7885
181
		 */
182
		if (!$input_errors) {
183
			$purpose = cert_get_purpose($_POST['cert'], false);
184
			if ($purpose['ca'] != 'Yes') {
185
				$input_errors[] = gettext("The submitted certificate does not appear to be a Certificate Authority, import it on the Certificates tab instead.");
186
			}
187
		}
188
	}
189
	if ($pconfig['method'] == "internal") {
190
		$reqdfields = explode(" ",
191
			"descr keylen lifetime dn_commonname");
192
		$reqdfieldsn = array(
193
			gettext("Descriptive name"),
194
			gettext("Key length"),
195
			gettext("Lifetime"),
196
			gettext("Distinguished name Common Name"));
197
	}
198
	if ($pconfig['method'] == "intermediate") {
199
		$reqdfields = explode(" ",
200
			"descr caref keylen lifetime dn_commonname");
201
		$reqdfieldsn = array(
202
			gettext("Descriptive name"),
203
			gettext("Signing Certificate Authority"),
204
			gettext("Key length"),
205
			gettext("Lifetime"),
206
			gettext("Distinguished name Common Name"));
207
	}
208

    
209
	do_input_validation($_POST, $reqdfields, $reqdfieldsn, $input_errors);
210
	if ($pconfig['method'] != "existing") {
211
		/* Make sure we do not have invalid characters in the fields for the certificate */
212
		if (preg_match("/[\?\>\<\&\/\\\"\']/", $_POST['descr'])) {
213
			array_push($input_errors, gettext("The field 'Descriptive Name' contains invalid characters."));
214
		}
215
		if (!in_array($_POST["keylen"], $ca_keylens)) {
216
			array_push($input_errors, gettext("Please select a valid Key Length."));
217
		}
218
		if (!in_array($_POST["digest_alg"], $openssl_digest_algs)) {
219
			array_push($input_errors, gettext("Please select a valid Digest Algorithm."));
220
		}
221
	}
222

    
223
	/* save modifications */
224
	if (!$input_errors) {
225
		$ca = array();
226
		if (!isset($pconfig['refid']) || empty($pconfig['refid'])) {
227
			$ca['refid'] = uniqid();
228
		} else {
229
			$ca['refid'] = $pconfig['refid'];
230
		}
231

    
232
		if (isset($id) && $a_ca[$id]) {
233
			$ca = $a_ca[$id];
234
		}
235

    
236
		$ca['descr'] = $pconfig['descr'];
237

    
238
		if ($act == "edit") {
239
			$ca['descr']  = $pconfig['descr'];
240
			$ca['refid']  = $pconfig['refid'];
241
			$ca['serial'] = $pconfig['serial'];
242
			$ca['crt']	  = base64_encode($pconfig['cert']);
243
			if (!empty($pconfig['key'])) {
244
				$ca['prv']	  = base64_encode($pconfig['key']);
245
			}
246
		} else {
247
			$old_err_level = error_reporting(0); /* otherwise openssl_ functions throw warnings directly to a page screwing menu tab */
248
			if ($pconfig['method'] == "existing") {
249
				ca_import($ca, $pconfig['cert'], $pconfig['key'], $pconfig['serial']);
250
			} else if ($pconfig['method'] == "internal") {
251
				$dn = array('commonName' => cert_escape_x509_chars($pconfig['dn_commonname']));
252
				if (!empty($pconfig['dn_country'])) {
253
					$dn['countryName'] = $pconfig['dn_country'];
254
				}
255
				if (!empty($pconfig['dn_state'])) {
256
					$dn['stateOrProvinceName'] = cert_escape_x509_chars($pconfig['dn_state']);
257
				}
258
				if (!empty($pconfig['dn_city'])) {
259
					$dn['localityName'] = cert_escape_x509_chars($pconfig['dn_city']);
260
				}
261
				if (!empty($pconfig['dn_organization'])) {
262
					$dn['organizationName'] = cert_escape_x509_chars($pconfig['dn_organization']);
263
				}
264
				if (!empty($pconfig['dn_organizationalunit'])) {
265
					$dn['organizationalUnitName'] = cert_escape_x509_chars($pconfig['dn_organizationalunit']);
266
				}
267
				if (!ca_create($ca, $pconfig['keylen'], $pconfig['lifetime'], $dn, $pconfig['digest_alg'])) {
268
					$input_errors = array();
269
					while ($ssl_err = openssl_error_string()) {
270
						if (strpos($ssl_err, 'NCONF_get_string:no value') === false) {
271
							array_push($input_errors, "openssl library returns: " . $ssl_err);
272
						}
273
					}
274
				}
275
			} else if ($pconfig['method'] == "intermediate") {
276
				$dn = array('commonName' => cert_escape_x509_chars($pconfig['dn_commonname']));
277
				if (!empty($pconfig['dn_country'])) {
278
					$dn['countryName'] = $pconfig['dn_country'];
279
				}
280
				if (!empty($pconfig['dn_state'])) {
281
					$dn['stateOrProvinceName'] = cert_escape_x509_chars($pconfig['dn_state']);
282
				}
283
				if (!empty($pconfig['dn_city'])) {
284
					$dn['localityName'] = cert_escape_x509_chars($pconfig['dn_city']);
285
				}
286
				if (!empty($pconfig['dn_organization'])) {
287
					$dn['organizationName'] = cert_escape_x509_chars($pconfig['dn_organization']);
288
				}
289
				if (!empty($pconfig['dn_organizationalunit'])) {
290
					$dn['organizationalUnitName'] = cert_escape_x509_chars($pconfig['dn_organizationalunit']);
291
				}
292
				if (!ca_inter_create($ca, $pconfig['keylen'], $pconfig['lifetime'], $dn, $pconfig['caref'], $pconfig['digest_alg'])) {
293
					$input_errors = array();
294
					while ($ssl_err = openssl_error_string()) {
295
						if (strpos($ssl_err, 'NCONF_get_string:no value') === false) {
296
							array_push($input_errors, "openssl library returns: " . $ssl_err);
297
						}
298
					}
299
				}
300
			}
301
			error_reporting($old_err_level);
302
		}
303

    
304
		if (isset($id) && $a_ca[$id]) {
305
			$a_ca[$id] = $ca;
306
		} else {
307
			$a_ca[] = $ca;
308
		}
309

    
310
		if (!$input_errors) {
311
			write_config();
312
			pfSenseHeader("system_camanager.php");
313
		}
314
	}
315
}
316

    
317
$pgtitle = array(gettext("System"), gettext("Certificate Manager"), gettext("CAs"));
318
$pglinks = array("", "system_camanager.php", "system_camanager.php");
319

    
320
if ($act == "new" || $act == "edit" || $act == gettext("Save") || $input_errors) {
321
	$pgtitle[] = gettext('Edit');
322
	$pglinks[] = "@self";
323
}
324
include("head.inc");
325

    
326
if ($input_errors) {
327
	print_input_errors($input_errors);
328
}
329

    
330
if ($savemsg) {
331
	print_info_box($savemsg, 'success');
332
}
333

    
334
// Load valid country codes
335
$dn_cc = array();
336
if (file_exists("/etc/ca_countries")) {
337
	$dn_cc_file=file("/etc/ca_countries");
338
	$dn_cc[''] = gettext("None");
339
	foreach ($dn_cc_file as $line) {
340
		if (preg_match('/^(\S*)\s(.*)$/', $line, $matches)) {
341
			$dn_cc[$matches[1]] = $matches[1];
342
		}
343
	}
344
}
345

    
346
$tab_array = array();
347
$tab_array[] = array(gettext("CAs"), true, "system_camanager.php");
348
$tab_array[] = array(gettext("Certificates"), false, "system_certmanager.php");
349
$tab_array[] = array(gettext("Certificate Revocation"), false, "system_crlmanager.php");
350
display_top_tabs($tab_array);
351

    
352
if (!($act == "new" || $act == "edit" || $act == gettext("Save") || $input_errors)) {
353
?>
354
<div class="panel panel-default">
355
	<div class="panel-heading"><h2 class="panel-title"><?=gettext('Certificate Authorities')?></h2></div>
356
	<div class="panel-body">
357
		<div class="table-responsive">
358
		<table class="table table-striped table-hover table-rowdblclickedit">
359
			<thead>
360
				<tr>
361
					<th><?=gettext("Name")?></th>
362
					<th><?=gettext("Internal")?></th>
363
					<th><?=gettext("Issuer")?></th>
364
					<th><?=gettext("Certificates")?></th>
365
					<th><?=gettext("Distinguished Name")?></th>
366
					<th><?=gettext("In Use")?></th>
367
					<th><?=gettext("Actions")?></th>
368
				</tr>
369
			</thead>
370
			<tbody>
371
<?php
372
$pluginparams = array();
373
$pluginparams['type'] = 'certificates';
374
$pluginparams['event'] = 'used_ca';
375
$certificates_used_by_packages = pkg_call_plugins('plugin_certificates', $pluginparams);
376

    
377
foreach ($a_ca as $i => $ca):
378
	$name = htmlspecialchars($ca['descr']);
379
	$subj = cert_get_subject($ca['crt']);
380
	$issuer = cert_get_issuer($ca['crt']);
381
	list($startdate, $enddate) = cert_get_dates($ca['crt']);
382
	if ($subj == $issuer) {
383
		$issuer_name = gettext("self-signed");
384
	} else {
385
		$issuer_name = gettext("external");
386
	}
387
	$subj = htmlspecialchars(cert_escape_x509_chars($subj, true));
388
	$issuer = htmlspecialchars($issuer);
389
	$certcount = 0;
390

    
391
	$issuer_ca = lookup_ca($ca['caref']);
392
	if ($issuer_ca) {
393
		$issuer_name = $issuer_ca['descr'];
394
	}
395

    
396
	foreach ($a_cert as $cert) {
397
		if ($cert['caref'] == $ca['refid']) {
398
			$certcount++;
399
		}
400
	}
401

    
402
	foreach ($a_ca as $cert) {
403
		if ($cert['caref'] == $ca['refid']) {
404
			$certcount++;
405
		}
406
	}
407
?>
408
				<tr>
409
					<td><?=$name?></td>
410
					<td><i class="fa fa-<?= (!empty($ca['prv'])) ? "check" : "times" ; ?>"></i></td>
411
					<td><i><?=$issuer_name?></i></td>
412
					<td><?=$certcount?></td>
413
					<td>
414
						<?=$subj?>
415
						<br />
416
						<small>
417
							<?=gettext("Valid From")?>: <b><?=$startdate ?></b><br /><?=gettext("Valid Until")?>: <b><?=$enddate ?></b>
418
						</small>
419
					</td>
420
					<td class="text-nowrap">
421
						<?php if (is_openvpn_server_ca($ca['refid'])): ?>
422
							<?=gettext("OpenVPN Server")?><br/>
423
						<?php endif?>
424
						<?php if (is_openvpn_client_ca($ca['refid'])): ?>
425
							<?=gettext("OpenVPN Client")?><br/>
426
						<?php endif?>
427
						<?php if (is_ipsec_peer_ca($ca['refid'])): ?>
428
							<?=gettext("IPsec Tunnel")?><br/>
429
						<?php endif?>
430
						<?php if (is_ldap_peer_ca($ca['refid'])): ?>
431
							<?=gettext("LDAP Server")?>
432
						<?php endif?>
433
						<?php echo cert_usedby_description($ca['refid'], $certificates_used_by_packages); ?>
434
					</td>
435
					<td class="text-nowrap">
436
						<a class="fa fa-pencil"	title="<?=gettext("Edit CA")?>"	href="system_camanager.php?act=edit&amp;id=<?=$i?>"></a>
437
						<a class="fa fa-certificate"	title="<?=gettext("Export CA")?>"	href="system_camanager.php?act=exp&amp;id=<?=$i?>"></a>
438
					<?php if ($ca['prv']): ?>
439
						<a class="fa fa-key"	title="<?=gettext("Export key")?>"	href="system_camanager.php?act=expkey&amp;id=<?=$i?>"></a>
440
					<?php endif?>
441
					<?php if (!ca_in_use($ca['refid'])): ?>
442
						<a class="fa fa-trash" 	title="<?=gettext("Delete CA and its CRLs")?>"	href="system_camanager.php?act=del&amp;id=<?=$i?>" usepost ></a>
443
					<?php endif?>
444
					</td>
445
				</tr>
446
<?php endforeach; ?>
447
			</tbody>
448
		</table>
449
		</div>
450
	</div>
451
</div>
452

    
453
<nav class="action-buttons">
454
	<a href="?act=new" class="btn btn-success btn-sm">
455
		<i class="fa fa-plus icon-embed-btn"></i>
456
		<?=gettext("Add")?>
457
	</a>
458
</nav>
459
<?php
460
	include("foot.inc");
461
	exit;
462
}
463

    
464
$form = new Form;
465
//$form->setAction('system_camanager.php?act=edit');
466
if (isset($id) && $a_ca[$id]) {
467
	$form->addGlobal(new Form_Input(
468
		'id',
469
		null,
470
		'hidden',
471
		$id
472
	));
473
}
474

    
475
if ($act == "edit") {
476
	$form->addGlobal(new Form_Input(
477
		'refid',
478
		null,
479
		'hidden',
480
		$pconfig['refid']
481
	));
482
}
483

    
484
$section = new Form_Section('Create / Edit CA');
485

    
486
$section->addInput(new Form_Input(
487
	'descr',
488
	'*Descriptive name',
489
	'text',
490
	$pconfig['descr']
491
));
492

    
493
if (!isset($id) || $act == "edit") {
494
	$section->addInput(new Form_Select(
495
		'method',
496
		'*Method',
497
		$pconfig['method'],
498
		$ca_methods
499
	))->toggles();
500
}
501

    
502
$form->add($section);
503

    
504
$section = new Form_Section('Existing Certificate Authority');
505
$section->addClass('toggle-existing collapse');
506

    
507
$section->addInput(new Form_Textarea(
508
	'cert',
509
	'*Certificate data',
510
	$pconfig['cert']
511
))->setHelp('Paste a certificate in X.509 PEM format here.');
512

    
513
$section->addInput(new Form_Textarea(
514
	'key',
515
	'Certificate Private Key (optional)',
516
	$pconfig['key']
517
))->setHelp('Paste the private key for the above certificate here. This is '.
518
	'optional in most cases, but is required when generating a '.
519
	'Certificate Revocation List (CRL).');
520

    
521
$section->addInput(new Form_Input(
522
	'serial',
523
	'Serial for next certificate',
524
	'number',
525
	$pconfig['serial']
526
))->setHelp('Enter a decimal number to be used as the serial number for the next '.
527
	'certificate to be created using this CA.');
528

    
529
$form->add($section);
530

    
531
$section = new Form_Section('Internal Certificate Authority');
532
$section->addClass('toggle-internal', 'toggle-intermediate', 'collapse');
533

    
534
$allCas = array();
535
foreach ($a_ca as $ca) {
536
	if (!$ca['prv']) {
537
			continue;
538
	}
539

    
540
	$allCas[ $ca['refid'] ] = $ca['descr'];
541
}
542

    
543
$group = new Form_Group('*Signing Certificate Authority');
544
$group->addClass('toggle-intermediate', 'collapse');
545
$group->add(new Form_Select(
546
	'caref',
547
	null,
548
	$pconfig['caref'],
549
	$allCas
550
));
551
$section->add($group);
552

    
553
$section->addInput(new Form_Select(
554
	'keylen',
555
	'*Key length (bits)',
556
	$pconfig['keylen'],
557
	array_combine($ca_keylens, $ca_keylens)
558
));
559

    
560
$section->addInput(new Form_Select(
561
	'digest_alg',
562
	'*Digest Algorithm',
563
	$pconfig['digest_alg'],
564
	array_combine($openssl_digest_algs, $openssl_digest_algs)
565
))->setHelp('NOTE: It is recommended to use an algorithm stronger than SHA1 '.
566
	'when possible.');
567

    
568
$section->addInput(new Form_Input(
569
	'lifetime',
570
	'*Lifetime (days)',
571
	'number',
572
	$pconfig['lifetime']
573
));
574

    
575
$section->addInput(new Form_Input(
576
	'dn_commonname',
577
	'*Common Name',
578
	'text',
579
	$pconfig['dn_commonname'],
580
	['placeholder' => 'e.g. internal-ca']
581
));
582

    
583
$section->addInput(new Form_StaticText(
584
	null,
585
	gettext('The following certificate authority subject components are optional and may be left blank.')
586
));
587

    
588
$section->addInput(new Form_Select(
589
	'dn_country',
590
	'Country Code',
591
	$pconfig['dn_country'],
592
	$dn_cc
593
));
594

    
595
$section->addInput(new Form_Input(
596
	'dn_state',
597
	'State or Province',
598
	'text',
599
	$pconfig['dn_state'],
600
	['placeholder' => 'e.g. Texas']
601
));
602

    
603
$section->addInput(new Form_Input(
604
	'dn_city',
605
	'City',
606
	'text',
607
	$pconfig['dn_city'],
608
	['placeholder' => 'e.g. Austin']
609
));
610

    
611
$section->addInput(new Form_Input(
612
	'dn_organization',
613
	'Organization',
614
	'text',
615
	$pconfig['dn_organization'],
616
	['placeholder' => 'e.g. My Company Inc']
617
));
618

    
619
$section->addInput(new Form_Input(
620
	'dn_organizationalunit',
621
	'Organizational Unit',
622
	'text',
623
	$pconfig['dn_organizationalunit'],
624
	['placeholder' => 'e.g. My Department Name (optional)']
625
));
626

    
627
$form->add($section);
628

    
629
print $form;
630

    
631
$internal_ca_count = 0;
632
foreach ($a_ca as $ca) {
633
	if ($ca['prv']) {
634
		$internal_ca_count++;
635
	}
636
}
637

    
638
include('foot.inc');
639
?>
(200-200/234)