Project

General

Profile

Download (17.4 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
	system_camanager.php
4
*/
5
/* ====================================================================
6
 *	Copyright (c)  2004-2015  Electric Sheep Fencing, LLC. All rights reserved.
7
 *  Copyright (c)  2008 Shrew Soft Inc.
8
 *
9
 *	Redistribution and use in source and binary forms, with or without modification,
10
 *	are permitted provided that the following conditions are met:
11
 *
12
 *	1. Redistributions of source code must retain the above copyright notice,
13
 *		this list of conditions and the following disclaimer.
14
 *
15
 *	2. Redistributions in binary form must reproduce the above copyright
16
 *		notice, this list of conditions and the following disclaimer in
17
 *		the documentation and/or other materials provided with the
18
 *		distribution.
19
 *
20
 *	3. All advertising materials mentioning features or use of this software
21
 *		must display the following acknowledgment:
22
 *		"This product includes software developed by the pfSense Project
23
 *		 for use in the pfSense software distribution. (http://www.pfsense.org/).
24
 *
25
 *	4. The names "pfSense" and "pfSense Project" must not be used to
26
 *		 endorse or promote products derived from this software without
27
 *		 prior written permission. For written permission, please contact
28
 *		 coreteam@pfsense.org.
29
 *
30
 *	5. Products derived from this software may not be called "pfSense"
31
 *		nor may "pfSense" appear in their names without prior written
32
 *		permission of the Electric Sheep Fencing, LLC.
33
 *
34
 *	6. Redistributions of any form whatsoever must retain the following
35
 *		acknowledgment:
36
 *
37
 *	"This product includes software developed by the pfSense Project
38
 *	for use in the pfSense software distribution (http://www.pfsense.org/).
39
 *
40
 *	THIS SOFTWARE IS PROVIDED BY THE pfSense PROJECT ``AS IS'' AND ANY
41
 *	EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
42
 *	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
43
 *	PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE pfSense PROJECT OR
44
 *	ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
45
 *	SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
46
 *	NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
47
 *	LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
48
 *	HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
49
 *	STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
50
 *	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
51
 *	OF THE POSSIBILITY OF SUCH DAMAGE.
52
 *
53
 *	====================================================================
54
 *
55
 */
56
/*
57
	pfSense_MODULE: certificate_manager
58
*/
59

    
60
##|+PRIV
61
##|*IDENT=page-system-camanager
62
##|*NAME=System: CA Manager
63
##|*DESCR=Allow access to the 'System: CA Manager' page.
64
##|*MATCH=system_camanager.php*
65
##|-PRIV
66

    
67
require("guiconfig.inc");
68
require_once("certs.inc");
69

    
70
$ca_methods = array(
71
	"existing" => gettext("Import an existing Certificate Authority"),
72
	"internal" => gettext("Create an internal Certificate Authority"),
73
	"intermediate" => gettext("Create an intermediate Certificate Authority"));
74

    
75
$ca_keylens = array("512", "1024", "2048", "4096");
76
$openssl_digest_algs = array("sha1", "sha224", "sha256", "sha384", "sha512");
77

    
78
$pgtitle = array(gettext("System"), gettext("Certificate Authority Manager"));
79

    
80
if (is_numericint($_GET['id'])) {
81
	$id = $_GET['id'];
82
}
83
if (isset($_POST['id']) && is_numericint($_POST['id'])) {
84
	$id = $_POST['id'];
85
}
86

    
87
if (!is_array($config['ca'])) {
88
	$config['ca'] = array();
89
}
90

    
91
$a_ca =& $config['ca'];
92

    
93
if (!is_array($config['cert'])) {
94
	$config['cert'] = array();
95
}
96

    
97
$a_cert =& $config['cert'];
98

    
99
if (!is_array($config['crl'])) {
100
	$config['crl'] = array();
101
}
102

    
103
$a_crl =& $config['crl'];
104

    
105
$act = $_GET['act'];
106
if ($_POST['act']) {
107
	$act = $_POST['act'];
108
}
109

    
110
if ($act == "del") {
111

    
112
	if (!isset($a_ca[$id])) {
113
		pfSenseHeader("system_camanager.php");
114
		exit;
115
	}
116

    
117
	$index = count($a_cert) - 1;
118
	for (;$index >= 0; $index--) {
119
		if ($a_cert[$index]['caref'] == $a_ca[$id]['refid']) {
120
			unset($a_cert[$index]);
121
		}
122
	}
123

    
124
	$index = count($a_crl) - 1;
125
	for (;$index >= 0; $index--) {
126
		if ($a_crl[$index]['caref'] == $a_ca[$id]['refid']) {
127
			unset($a_crl[$index]);
128
		}
129
	}
130

    
131
	$name = $a_ca[$id]['descr'];
132
	unset($a_ca[$id]);
133
	write_config();
134
	$savemsg = sprintf(gettext("Certificate Authority %s and its CRLs (if any) successfully deleted"), htmlspecialchars($name)) . "<br />";
135
	pfSenseHeader("system_camanager.php");
136
	exit;
137
}
138

    
139
if ($act == "edit") {
140
	if (!$a_ca[$id]) {
141
		pfSenseHeader("system_camanager.php");
142
		exit;
143
	}
144
	$pconfig['descr']  = $a_ca[$id]['descr'];
145
	$pconfig['refid']  = $a_ca[$id]['refid'];
146
	$pconfig['cert']   = base64_decode($a_ca[$id]['crt']);
147
	$pconfig['serial'] = $a_ca[$id]['serial'];
148
	if (!empty($a_ca[$id]['prv'])) {
149
		$pconfig['key'] = base64_decode($a_ca[$id]['prv']);
150
	}
151
}
152

    
153
if ($act == "new") {
154
	$pconfig['method'] = $_GET['method'];
155
	$pconfig['keylen'] = "2048";
156
	$pconfig['digest_alg'] = "sha256";
157
	$pconfig['lifetime'] = "3650";
158
	$pconfig['dn_commonname'] = "internal-ca";
159
}
160

    
161
if ($act == "exp") {
162

    
163
	if (!$a_ca[$id]) {
164
		pfSenseHeader("system_camanager.php");
165
		exit;
166
	}
167

    
168
	$exp_name = urlencode("{$a_ca[$id]['descr']}.crt");
169
	$exp_data = base64_decode($a_ca[$id]['crt']);
170
	$exp_size = strlen($exp_data);
171

    
172
	header("Content-Type: application/octet-stream");
173
	header("Content-Disposition: attachment; filename={$exp_name}");
174
	header("Content-Length: $exp_size");
175
	echo $exp_data;
176
	exit;
177
}
178

    
179
if ($act == "expkey") {
180

    
181
	if (!$a_ca[$id]) {
182
		pfSenseHeader("system_camanager.php");
183
		exit;
184
	}
185

    
186
	$exp_name = urlencode("{$a_ca[$id]['descr']}.key");
187
	$exp_data = base64_decode($a_ca[$id]['prv']);
188
	$exp_size = strlen($exp_data);
189

    
190
	header("Content-Type: application/octet-stream");
191
	header("Content-Disposition: attachment; filename={$exp_name}");
192
	header("Content-Length: $exp_size");
193
	echo $exp_data;
194
	exit;
195
}
196

    
197
if ($_POST) {
198

    
199
	unset($input_errors);
200
	$input_errors = array();
201
	$pconfig = $_POST;
202

    
203
	/* input validation */
204
	if ($pconfig['method'] == "existing") {
205
		$reqdfields = explode(" ", "descr cert");
206
		$reqdfieldsn = array(
207
			gettext("Descriptive name"),
208
			gettext("Certificate data"));
209
		if ($_POST['cert'] && (!strstr($_POST['cert'], "BEGIN CERTIFICATE") || !strstr($_POST['cert'], "END CERTIFICATE"))) {
210
			$input_errors[] = gettext("This certificate does not appear to be valid.");
211
		}
212
		if ($_POST['key'] && strstr($_POST['key'], "ENCRYPTED")) {
213
			$input_errors[] = gettext("Encrypted private keys are not yet supported.");
214
		}
215
	}
216
	if ($pconfig['method'] == "internal") {
217
		$reqdfields = explode(" ",
218
			"descr keylen lifetime dn_country dn_state dn_city ".
219
			"dn_organization dn_email dn_commonname");
220
		$reqdfieldsn = array(
221
			gettext("Descriptive name"),
222
			gettext("Key length"),
223
			gettext("Lifetime"),
224
			gettext("Distinguished name Country Code"),
225
			gettext("Distinguished name State or Province"),
226
			gettext("Distinguished name City"),
227
			gettext("Distinguished name Organization"),
228
			gettext("Distinguished name Email Address"),
229
			gettext("Distinguished name Common Name"));
230
	}
231
	if ($pconfig['method'] == "intermediate") {
232
		$reqdfields = explode(" ",
233
			"descr caref keylen lifetime dn_country dn_state dn_city ".
234
			"dn_organization dn_email dn_commonname");
235
		$reqdfieldsn = array(
236
			gettext("Descriptive name"),
237
			gettext("Signing Certificate Authority"),
238
			gettext("Key length"),
239
			gettext("Lifetime"),
240
			gettext("Distinguished name Country Code"),
241
			gettext("Distinguished name State or Province"),
242
			gettext("Distinguished name City"),
243
			gettext("Distinguished name Organization"),
244
			gettext("Distinguished name Email Address"),
245
			gettext("Distinguished name Common Name"));
246
	}
247

    
248
	do_input_validation($_POST, $reqdfields, $reqdfieldsn, $input_errors);
249
	if ($pconfig['method'] != "existing") {
250
		/* Make sure we do not have invalid characters in the fields for the certificate */
251
		if (preg_match("/[\?\>\<\&\/\\\"\']/", $_POST['descr'])) {
252
			array_push($input_errors, "The field 'Descriptive Name' contains invalid characters.");
253
		}
254

    
255
		for ($i = 0; $i < count($reqdfields); $i++) {
256
			if ($reqdfields[$i] == 'dn_email') {
257
				if (preg_match("/[\!\#\$\%\^\(\)\~\?\>\<\&\/\\\,\"\']/", $_POST["dn_email"])) {
258
					array_push($input_errors, "The field 'Distinguished name Email Address' contains invalid characters.");
259
				}
260
			} else if ($reqdfields[$i] == 'dn_commonname') {
261
				if (preg_match("/[\!\@\#\$\%\^\(\)\~\?\>\<\&\/\\\,\"\']/", $_POST["dn_commonname"])) {
262
					array_push($input_errors, "The field 'Distinguished name Common Name' contains invalid characters.");
263
				}
264
			} else if (($reqdfields[$i] != "descr") && preg_match("/[\!\@\#\$\%\^\(\)\~\?\>\<\&\/\\\,\.\"\']/", $_POST["$reqdfields[$i]"])) {
265
				array_push($input_errors, "The field '" . $reqdfieldsn[$i] . "' contains invalid characters.");
266
			}
267
		}
268
		if (!in_array($_POST["keylen"], $ca_keylens)) {
269
			array_push($input_errors, gettext("Please select a valid Key Length."));
270
		}
271
		if (!in_array($_POST["digest_alg"], $openssl_digest_algs)) {
272
			array_push($input_errors, gettext("Please select a valid Digest Algorithm."));
273
		}
274
	}
275

    
276
	/* if this is an AJAX caller then handle via JSON */
277
	if (isAjax() && is_array($input_errors)) {
278
		input_errors2Ajax($input_errors);
279
		exit;
280
	}
281

    
282
	/* save modifications */
283
	if (!$input_errors) {
284
		$ca = array();
285
		if (!isset($pconfig['refid']) || empty($pconfig['refid'])) {
286
			$ca['refid'] = uniqid();
287
		} else {
288
			$ca['refid'] = $pconfig['refid'];
289
		}
290

    
291
		if (isset($id) && $a_ca[$id]) {
292
			$ca = $a_ca[$id];
293
		}
294

    
295
		$ca['descr'] = $pconfig['descr'];
296

    
297
		if ($act == "edit") {
298
			$ca['descr']  = $pconfig['descr'];
299
			$ca['refid']  = $pconfig['refid'];
300
			$ca['serial'] = $pconfig['serial'];
301
			$ca['crt']	  = base64_encode($pconfig['cert']);
302
			if (!empty($pconfig['key'])) {
303
				$ca['prv']	  = base64_encode($pconfig['key']);
304
			}
305
		} else {
306
			$old_err_level = error_reporting(0); /* otherwise openssl_ functions throw warnings directly to a page screwing menu tab */
307
			if ($pconfig['method'] == "existing") {
308
				ca_import($ca, $pconfig['cert'], $pconfig['key'], $pconfig['serial']);
309
			} else if ($pconfig['method'] == "internal") {
310
				$dn = array(
311
					'countryName' => $pconfig['dn_country'],
312
					'stateOrProvinceName' => $pconfig['dn_state'],
313
					'localityName' => $pconfig['dn_city'],
314
					'organizationName' => $pconfig['dn_organization'],
315
					'emailAddress' => $pconfig['dn_email'],
316
					'commonName' => $pconfig['dn_commonname']);
317
				if (!ca_create($ca, $pconfig['keylen'], $pconfig['lifetime'], $dn, $pconfig['digest_alg'])) {
318
					while ($ssl_err = openssl_error_string()) {
319
						$input_errors = array();
320
						array_push($input_errors, "openssl library returns: " . $ssl_err);
321
					}
322
				}
323
			}
324
			else if ($pconfig['method'] == "intermediate") {
325
				$dn = array(
326
					'countryName' => $pconfig['dn_country'],
327
					'stateOrProvinceName' => $pconfig['dn_state'],
328
					'localityName' => $pconfig['dn_city'],
329
					'organizationName' => $pconfig['dn_organization'],
330
					'emailAddress' => $pconfig['dn_email'],
331
					'commonName' => $pconfig['dn_commonname']);
332

    
333
				if (!ca_inter_create($ca, $pconfig['keylen'], $pconfig['lifetime'], $dn, $pconfig['caref'], $pconfig['digest_alg'])) {
334
					while ($ssl_err = openssl_error_string()) {
335
						$input_errors = array();
336
						array_push($input_errors, "openssl library returns: " . $ssl_err);
337
					}
338
				}
339
			}
340
			error_reporting($old_err_level);
341
		}
342

    
343
		if (isset($id) && $a_ca[$id]) {
344
			$a_ca[$id] = $ca;
345
		} else {
346
			$a_ca[] = $ca;
347
		}
348

    
349
		if (!$input_errors) {
350
			write_config();
351
		}
352

    
353
		pfSenseHeader("system_camanager.php");
354
	}
355
}
356

    
357
include("head.inc");
358

    
359
if ($input_errors)
360
	print_input_errors($input_errors);
361

    
362
if ($savemsg)
363
	print_info_box($savemsg, 'success');
364

    
365
// Load valid country codes
366
$dn_cc = array();
367
if (file_exists("/etc/ca_countries")){
368
	$dn_cc_file=file("/etc/ca_countries");
369
	foreach($dn_cc_file as $line) {
370
		if (preg_match('/^(\S*)\s(.*)$/', $line, $matches)) {
371
			$dn_cc[$matches[1]] = $matches[1];
372
		}
373
	}
374
}
375

    
376
$tab_array = array();
377
$tab_array[] = array(gettext("CAs"), true, "system_camanager.php");
378
$tab_array[] = array(gettext("Certificates"), false, "system_certmanager.php");
379
$tab_array[] = array(gettext("Certificate Revocation"), false, "system_crlmanager.php");
380
display_top_tabs($tab_array);
381

    
382
if (!($act == "new" || $act == "edit" || $act == gettext("Save") || $input_errors))
383
{
384
?>
385
<div class="table-responsive">
386
<table class="table table-striped table-hover">
387
	<thead>
388
		<tr>
389
			<th><?=gettext("Name")?></th>
390
			<th><?=gettext("Internal")?></th>
391
			<th><?=gettext("Issuer")?></th>
392
			<th><?=gettext("Certificates")?></th>
393
			<th><?=gettext("Distinguished Name")?></th>
394
			<th><?=gettext("Actions")?></th>
395
		</tr>
396
	</thead>
397
	<tbody>
398
<?php
399
foreach ($a_ca as $i => $ca):
400
	$name = htmlspecialchars($ca['descr']);
401
	$subj = cert_get_subject($ca['crt']);
402
	$issuer = cert_get_issuer($ca['crt']);
403
	list($startdate, $enddate) = cert_get_dates($ca['crt']);
404
	if ($subj == $issuer)
405
		$issuer_name = gettext("self-signed");
406
	else
407
		$issuer_name = gettext("external");
408
	$subj = htmlspecialchars($subj);
409
	$issuer = htmlspecialchars($issuer);
410
	$certcount = 0;
411

    
412
	$issuer_ca = lookup_ca($ca['caref']);
413
	if ($issuer_ca)
414
		$issuer_name = $issuer_ca['descr'];
415

    
416
	// TODO : Need gray certificate icon
417
	$internal = (!!$ca['prv']);
418

    
419
	foreach ($a_cert as $cert)
420
		if ($cert['caref'] == $ca['refid'])
421
			$certcount++;
422

    
423
	foreach ($a_ca as $cert)
424
		if ($cert['caref'] == $ca['refid'])
425
			$certcount++;
426
?>
427
		<tr>
428
			<td><?=$name?></td>
429
			<td><?=$internal?></td>
430
			<td><i><?=$issuer_name?></i></td>
431
			<td><?=$certcount?></td>
432
			<td>
433
				<?=$subj?>
434
				<br />
435
				<small>
436
					<?=gettext("Valid From")?>: <b><?=$startdate ?></b><br /><?=gettext("Valid Until")?>: <b><?=$enddate ?></b>
437
				</small>
438
			</td>
439
			<td>
440
				<a class="fa fa-pencil"	title="<?=gettext("Edit")?>"	href="system_camanager.php?act=edit&amp;id=<?=$i?>"></a>
441
				<a class="fa fa-sign-in"	title="<?=gettext("Export")?>"	href="system_camanager.php?act=exp&amp;id=<?=$i?>"></a>
442
			<?php if ($ca['prv']): ?>
443
				<a class="fa fa-key"	title="<?=gettext("Export key")?>"	href="system_camanager.php?act=expkey&amp;id=<?=$i?>"></a>
444
			<?php endif?>
445
				<a class="fa fa-trash" 	title="<?=gettext("Delete")?>"	href="system_camanager.php?act=del&amp;id=<?=$i?>"></a>
446
			</td>
447
		</tr>
448
<?php endforeach; ?>
449
	</tbody>
450
</table>
451

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

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

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

    
485
$section = new Form_Section('Create / edit CA');
486

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

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

    
504
$form->add($section);
505

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

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

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

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

    
531
$form->add($section);
532

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

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

    
542
	$allCas[ $ca['refid'] ] = $ca['descr'];
543
}
544

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

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

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

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

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

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

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

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

    
608
$section->addInput(new Form_Input(
609
	'dn_email',
610
	'Email Address',
611
	'email',
612
	$pconfig['dn_email'],
613
	['placeholder' => 'e.g. admin@mycompany.com']
614
));
615

    
616
$section->addInput(new Form_Input(
617
	'dn_commonname',
618
	'Common Name',
619
	'text',
620
	$pconfig['dn_commonname'],
621
	['placeholder' => 'e.g. internal-ca']
622
));
623

    
624
$form->add($section);
625

    
626
print $form;
627

    
628
$internal_ca_count = 0;
629
foreach ($a_ca as $ca) {
630
	if ($ca['prv']) {
631
		$internal_ca_count++;
632
	}
633
}
634

    
635
include('foot.inc');
(192-192/228)