Project

General

Profile

Download (17.5 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
##|+PRIV
58
##|*IDENT=page-system-camanager
59
##|*NAME=System: CA Manager
60
##|*DESCR=Allow access to the 'System: CA Manager' page.
61
##|*MATCH=system_camanager.php*
62
##|-PRIV
63

    
64
require("guiconfig.inc");
65
require_once("certs.inc");
66

    
67
$ca_methods = array(
68
	"existing" => gettext("Import an existing Certificate Authority"),
69
	"internal" => gettext("Create an internal Certificate Authority"),
70
	"intermediate" => gettext("Create an intermediate Certificate Authority"));
71

    
72
$ca_keylens = array("512", "1024", "2048", "4096");
73
$openssl_digest_algs = array("sha1", "sha224", "sha256", "sha384", "sha512");
74

    
75
$pgtitle = array(gettext("System"), gettext("Certificate Manager"), gettext("CAs"));
76

    
77
if (is_numericint($_GET['id'])) {
78
	$id = $_GET['id'];
79
}
80
if (isset($_POST['id']) && is_numericint($_POST['id'])) {
81
	$id = $_POST['id'];
82
}
83

    
84
if (!is_array($config['ca'])) {
85
	$config['ca'] = array();
86
}
87

    
88
$a_ca =& $config['ca'];
89

    
90
if (!is_array($config['cert'])) {
91
	$config['cert'] = array();
92
}
93

    
94
$a_cert =& $config['cert'];
95

    
96
if (!is_array($config['crl'])) {
97
	$config['crl'] = array();
98
}
99

    
100
$a_crl =& $config['crl'];
101

    
102
$act = $_GET['act'];
103
if ($_POST['act']) {
104
	$act = $_POST['act'];
105
}
106

    
107
if ($act == "del") {
108

    
109
	if (!isset($a_ca[$id])) {
110
		pfSenseHeader("system_camanager.php");
111
		exit;
112
	}
113

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

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

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

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

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

    
158
if ($act == "exp") {
159

    
160
	if (!$a_ca[$id]) {
161
		pfSenseHeader("system_camanager.php");
162
		exit;
163
	}
164

    
165
	$exp_name = urlencode("{$a_ca[$id]['descr']}.crt");
166
	$exp_data = base64_decode($a_ca[$id]['crt']);
167
	$exp_size = strlen($exp_data);
168

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

    
176
if ($act == "expkey") {
177

    
178
	if (!$a_ca[$id]) {
179
		pfSenseHeader("system_camanager.php");
180
		exit;
181
	}
182

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

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

    
194
if ($_POST) {
195

    
196
	unset($input_errors);
197
	$input_errors = array();
198
	$pconfig = $_POST;
199

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

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

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

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

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

    
288
		if (isset($id) && $a_ca[$id]) {
289
			$ca = $a_ca[$id];
290
		}
291

    
292
		$ca['descr'] = $pconfig['descr'];
293

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

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

    
339
		if (isset($id) && $a_ca[$id]) {
340
			$a_ca[$id] = $ca;
341
		} else {
342
			$a_ca[] = $ca;
343
		}
344

    
345
		if (!$input_errors) {
346
			write_config();
347
		}
348

    
349
		pfSenseHeader("system_camanager.php");
350
	}
351
}
352

    
353
include("head.inc");
354

    
355
if ($input_errors) {
356
	print_input_errors($input_errors);
357
}
358

    
359
if ($savemsg) {
360
	print_info_box($savemsg, 'success');
361
}
362

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

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

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

    
410
	$issuer_ca = lookup_ca($ca['caref']);
411
	if ($issuer_ca) {
412
		$issuer_name = $issuer_ca['descr'];
413
	}
414

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

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

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

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

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

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

    
487
$section = new Form_Section('Create / edit CA');
488

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
627
print $form;
628

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

    
636
include('foot.inc');
637
?>
(195-195/229)