Project

General

Profile

Download (17.6 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-2019 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
init_config_arr(array('ca'));
47
$a_ca = &$config['ca'];
48

    
49
init_config_arr(array('cert'));
50
$a_cert = &$config['cert'];
51

    
52
init_config_arr(array('crl'));
53
$a_crl = &$config['crl'];
54

    
55
if ($_REQUEST['act']) {
56
	$act = $_REQUEST['act'];
57
}
58

    
59
if ($_POST['act'] == "del") {
60

    
61
	if (!isset($a_ca[$id])) {
62
		pfSenseHeader("system_camanager.php");
63
		exit;
64
	}
65

    
66
	/* Only remove CA reference when deleting. It can be reconnected if a new matching CA is imported */
67
	$index = count($a_cert) - 1;
68
	for (;$index >= 0; $index--) {
69
		if ($a_cert[$index]['caref'] == $a_ca[$id]['refid']) {
70
			unset($a_cert[$index]['caref']);
71
		}
72
	}
73

    
74
	/* Remove any CRLs for this CA, there is no way to recover the connection once the CA has been removed. */
75
	$index = count($a_crl) - 1;
76
	for (;$index >= 0; $index--) {
77
		if ($a_crl[$index]['caref'] == $a_ca[$id]['refid']) {
78
			unset($a_crl[$index]);
79
		}
80
	}
81

    
82
	$name = $a_ca[$id]['descr'];
83
	unset($a_ca[$id]);
84
	write_config();
85
	$savemsg = sprintf(gettext("Certificate Authority %s and its CRLs (if any) successfully deleted."), htmlspecialchars($name));
86
	pfSenseHeader("system_camanager.php");
87
	exit;
88
}
89

    
90
if ($act == "edit") {
91
	if (!$a_ca[$id]) {
92
		pfSenseHeader("system_camanager.php");
93
		exit;
94
	}
95
	$pconfig['method'] = 'existing';
96
	$pconfig['descr']  = $a_ca[$id]['descr'];
97
	$pconfig['refid']  = $a_ca[$id]['refid'];
98
	$pconfig['cert']   = base64_decode($a_ca[$id]['crt']);
99
	$pconfig['serial'] = $a_ca[$id]['serial'];
100
	if (!empty($a_ca[$id]['prv'])) {
101
		$pconfig['key'] = base64_decode($a_ca[$id]['prv']);
102
	}
103
}
104

    
105
if ($act == "new") {
106
	$pconfig['method'] = $_POST['method'];
107
	$pconfig['keylen'] = "2048";
108
	$pconfig['digest_alg'] = "sha256";
109
	$pconfig['lifetime'] = "3650";
110
	$pconfig['dn_commonname'] = "internal-ca";
111
}
112

    
113
if ($act == "exp") {
114

    
115
	if (!$a_ca[$id]) {
116
		pfSenseHeader("system_camanager.php");
117
		exit;
118
	}
119

    
120
	$exp_name = urlencode("{$a_ca[$id]['descr']}.crt");
121
	$exp_data = base64_decode($a_ca[$id]['crt']);
122
	$exp_size = strlen($exp_data);
123

    
124
	header("Content-Type: application/octet-stream");
125
	header("Content-Disposition: attachment; filename={$exp_name}");
126
	header("Content-Length: $exp_size");
127
	echo $exp_data;
128
	exit;
129
}
130

    
131
if ($act == "expkey") {
132

    
133
	if (!$a_ca[$id]) {
134
		pfSenseHeader("system_camanager.php");
135
		exit;
136
	}
137

    
138
	$exp_name = urlencode("{$a_ca[$id]['descr']}.key");
139
	$exp_data = base64_decode($a_ca[$id]['prv']);
140
	$exp_size = strlen($exp_data);
141

    
142
	header("Content-Type: application/octet-stream");
143
	header("Content-Disposition: attachment; filename={$exp_name}");
144
	header("Content-Length: $exp_size");
145
	echo $exp_data;
146
	exit;
147
}
148

    
149
if ($_POST['save']) {
150

    
151
	unset($input_errors);
152
	$input_errors = array();
153
	$pconfig = $_POST;
154

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

    
200
	do_input_validation($_POST, $reqdfields, $reqdfieldsn, $input_errors);
201
	if ($pconfig['method'] != "existing") {
202
		/* Make sure we do not have invalid characters in the fields for the certificate */
203
		if (preg_match("/[\?\>\<\&\/\\\"\']/", $_POST['descr'])) {
204
			array_push($input_errors, gettext("The field 'Descriptive Name' contains invalid characters."));
205
		}
206
		if (!in_array($_POST["keylen"], $ca_keylens)) {
207
			array_push($input_errors, gettext("Please select a valid Key Length."));
208
		}
209
		if (!in_array($_POST["digest_alg"], $openssl_digest_algs)) {
210
			array_push($input_errors, gettext("Please select a valid Digest Algorithm."));
211
		}
212
	}
213

    
214
	/* save modifications */
215
	if (!$input_errors) {
216
		$ca = array();
217
		if (!isset($pconfig['refid']) || empty($pconfig['refid'])) {
218
			$ca['refid'] = uniqid();
219
		} else {
220
			$ca['refid'] = $pconfig['refid'];
221
		}
222

    
223
		if (isset($id) && $a_ca[$id]) {
224
			$ca = $a_ca[$id];
225
		}
226

    
227
		$ca['descr'] = $pconfig['descr'];
228

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

    
295
		if (isset($id) && $a_ca[$id]) {
296
			$a_ca[$id] = $ca;
297
		} else {
298
			$a_ca[] = $ca;
299
		}
300

    
301
		if (!$input_errors) {
302
			write_config();
303
			pfSenseHeader("system_camanager.php");
304
		}
305
	}
306
}
307

    
308
$pgtitle = array(gettext("System"), gettext("Certificate Manager"), gettext("CAs"));
309
$pglinks = array("", "system_camanager.php", "system_camanager.php");
310

    
311
if ($act == "new" || $act == "edit" || $act == gettext("Save") || $input_errors) {
312
	$pgtitle[] = gettext('Edit');
313
	$pglinks[] = "@self";
314
}
315
include("head.inc");
316

    
317
if ($input_errors) {
318
	print_input_errors($input_errors);
319
}
320

    
321
if ($savemsg) {
322
	print_info_box($savemsg, 'success');
323
}
324

    
325
// Load valid country codes
326
$dn_cc = array();
327
if (file_exists("/etc/ca_countries")) {
328
	$dn_cc_file=file("/etc/ca_countries");
329
	$dn_cc[''] = gettext("None");
330
	foreach ($dn_cc_file as $line) {
331
		if (preg_match('/^(\S*)\s(.*)$/', $line, $matches)) {
332
			$dn_cc[$matches[1]] = $matches[1];
333
		}
334
	}
335
}
336

    
337
$tab_array = array();
338
$tab_array[] = array(gettext("CAs"), true, "system_camanager.php");
339
$tab_array[] = array(gettext("Certificates"), false, "system_certmanager.php");
340
$tab_array[] = array(gettext("Certificate Revocation"), false, "system_crlmanager.php");
341
display_top_tabs($tab_array);
342

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

    
368
foreach ($a_ca as $i => $ca):
369
	$name = htmlspecialchars($ca['descr']);
370
	$subj = cert_get_subject($ca['crt']);
371
	$issuer = cert_get_issuer($ca['crt']);
372
	list($startdate, $enddate) = cert_get_dates($ca['crt']);
373
	if ($subj == $issuer) {
374
		$issuer_name = gettext("self-signed");
375
	} else {
376
		$issuer_name = gettext("external");
377
	}
378
	$subj = htmlspecialchars(cert_escape_x509_chars($subj, true));
379
	$issuer = htmlspecialchars($issuer);
380
	$certcount = 0;
381

    
382
	$issuer_ca = lookup_ca($ca['caref']);
383
	if ($issuer_ca) {
384
		$issuer_name = $issuer_ca['descr'];
385
	}
386

    
387
	foreach ($a_cert as $cert) {
388
		if ($cert['caref'] == $ca['refid']) {
389
			$certcount++;
390
		}
391
	}
392

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

    
444
<nav class="action-buttons">
445
	<a href="?act=new" class="btn btn-success btn-sm">
446
		<i class="fa fa-plus icon-embed-btn"></i>
447
		<?=gettext("Add")?>
448
	</a>
449
</nav>
450
<?php
451
	include("foot.inc");
452
	exit;
453
}
454

    
455
$form = new Form;
456
//$form->setAction('system_camanager.php?act=edit');
457
if (isset($id) && $a_ca[$id]) {
458
	$form->addGlobal(new Form_Input(
459
		'id',
460
		null,
461
		'hidden',
462
		$id
463
	));
464
}
465

    
466
if ($act == "edit") {
467
	$form->addGlobal(new Form_Input(
468
		'refid',
469
		null,
470
		'hidden',
471
		$pconfig['refid']
472
	));
473
}
474

    
475
$section = new Form_Section('Create / Edit CA');
476

    
477
$section->addInput(new Form_Input(
478
	'descr',
479
	'*Descriptive name',
480
	'text',
481
	$pconfig['descr']
482
));
483

    
484
if (!isset($id) || $act == "edit") {
485
	$section->addInput(new Form_Select(
486
		'method',
487
		'*Method',
488
		$pconfig['method'],
489
		$ca_methods
490
	))->toggles();
491
}
492

    
493
$form->add($section);
494

    
495
$section = new Form_Section('Existing Certificate Authority');
496
$section->addClass('toggle-existing collapse');
497

    
498
$section->addInput(new Form_Textarea(
499
	'cert',
500
	'*Certificate data',
501
	$pconfig['cert']
502
))->setHelp('Paste a certificate in X.509 PEM format here.');
503

    
504
$section->addInput(new Form_Textarea(
505
	'key',
506
	'Certificate Private Key (optional)',
507
	$pconfig['key']
508
))->setHelp('Paste the private key for the above certificate here. This is '.
509
	'optional in most cases, but is required when generating a '.
510
	'Certificate Revocation List (CRL).');
511

    
512
$section->addInput(new Form_Input(
513
	'serial',
514
	'Serial for next certificate',
515
	'number',
516
	$pconfig['serial']
517
))->setHelp('Enter a decimal number to be used as the serial number for the next '.
518
	'certificate to be created using this CA.');
519

    
520
$form->add($section);
521

    
522
$section = new Form_Section('Internal Certificate Authority');
523
$section->addClass('toggle-internal', 'toggle-intermediate', 'collapse');
524

    
525
$allCas = array();
526
foreach ($a_ca as $ca) {
527
	if (!$ca['prv']) {
528
			continue;
529
	}
530

    
531
	$allCas[ $ca['refid'] ] = $ca['descr'];
532
}
533

    
534
$group = new Form_Group('*Signing Certificate Authority');
535
$group->addClass('toggle-intermediate', 'collapse');
536
$group->add(new Form_Select(
537
	'caref',
538
	null,
539
	$pconfig['caref'],
540
	$allCas
541
));
542
$section->add($group);
543

    
544
$section->addInput(new Form_Select(
545
	'keylen',
546
	'*Key length (bits)',
547
	$pconfig['keylen'],
548
	array_combine($ca_keylens, $ca_keylens)
549
));
550

    
551
$section->addInput(new Form_Select(
552
	'digest_alg',
553
	'*Digest Algorithm',
554
	$pconfig['digest_alg'],
555
	array_combine($openssl_digest_algs, $openssl_digest_algs)
556
))->setHelp('NOTE: It is recommended to use an algorithm stronger than SHA1 '.
557
	'when possible.');
558

    
559
$section->addInput(new Form_Input(
560
	'lifetime',
561
	'*Lifetime (days)',
562
	'number',
563
	$pconfig['lifetime']
564
));
565

    
566
$section->addInput(new Form_Input(
567
	'dn_commonname',
568
	'*Common Name',
569
	'text',
570
	$pconfig['dn_commonname'],
571
	['placeholder' => 'e.g. internal-ca']
572
));
573

    
574
$section->addInput(new Form_StaticText(
575
	null,
576
	gettext('The following certificate authority subject components are optional and may be left blank.')
577
));
578

    
579
$section->addInput(new Form_Select(
580
	'dn_country',
581
	'Country Code',
582
	$pconfig['dn_country'],
583
	$dn_cc
584
));
585

    
586
$section->addInput(new Form_Input(
587
	'dn_state',
588
	'State or Province',
589
	'text',
590
	$pconfig['dn_state'],
591
	['placeholder' => 'e.g. Texas']
592
));
593

    
594
$section->addInput(new Form_Input(
595
	'dn_city',
596
	'City',
597
	'text',
598
	$pconfig['dn_city'],
599
	['placeholder' => 'e.g. Austin']
600
));
601

    
602
$section->addInput(new Form_Input(
603
	'dn_organization',
604
	'Organization',
605
	'text',
606
	$pconfig['dn_organization'],
607
	['placeholder' => 'e.g. My Company Inc']
608
));
609

    
610
$section->addInput(new Form_Input(
611
	'dn_organizationalunit',
612
	'Organizational Unit',
613
	'text',
614
	$pconfig['dn_organizationalunit'],
615
	['placeholder' => 'e.g. My Department Name (optional)']
616
));
617

    
618
$form->add($section);
619

    
620
print $form;
621

    
622
$internal_ca_count = 0;
623
foreach ($a_ca as $ca) {
624
	if ($ca['prv']) {
625
		$internal_ca_count++;
626
	}
627
}
628

    
629
include('foot.inc');
630
?>
(200-200/234)