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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
196
if ($_POST) {
197

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

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

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

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

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

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

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

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

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

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

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

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

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

    
356
include("head.inc");
357

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

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

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

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

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

    
411
	$issuer_ca = lookup_ca($ca['caref']);
412
	if ($issuer_ca)
413
		$issuer_name = $issuer_ca['descr'];
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
	foreach ($a_ca as $cert)
423
		if ($cert['caref'] == $ca['refid'])
424
			$certcount++;
425
?>
426
		<tr>
427
			<td><?=$name?></td>
428
			<td><?=$internal?></td>
429
			<td><i><?=$issuer_name?></i></td>
430
			<td><?=$certcount?></td>
431
			<td>
432
				<?=$subj?>
433
				<br />
434
				<small>
435
					<?=gettext("Valid From")?>: <b><?=$startdate ?></b><br /><?=gettext("Valid Until")?>: <b><?=$enddate ?></b>
436
				</small>
437
			</td>
438
			<td>
439
				<a href="system_camanager.php?act=edit&amp;id=<?=$i?>" class="btn btn-xs btn-primary">
440
					<?=gettext("edit")?>
441
				</a>
442
				<a href="system_camanager.php?act=exp&amp;id=<?=$i?>" class="btn btn-xs btn-default">
443
					<?=gettext("export cert")?>
444
				</a>
445
				<?php if ($ca['prv']): ?>
446
					<a href="system_camanager.php?act=expkey&amp;id=<?=$i?>" class="btn btn-xs btn-default">
447
						<?=gettext("export private key")?>
448
					</a>
449
				<?php endif?>
450
				<a href="system_camanager.php?act=del&amp;id=<?=$i?>" class="btn btn-xs btn-danger">
451
					<?=gettext("delete")?>
452
				</a>
453
			</td>
454
		</tr>
455
<?php endforeach; ?>
456
	</tbody>
457
</table>
458

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

    
470
require_once('classes/Form.class.php');
471
$form = new Form;
472
//$form->setAction('system_camanager.php?act=edit');
473
if (isset($id) && $a_ca[$id])
474
{
475
	$form->addGlobal(new Form_Input(
476
		'id',
477
		null,
478
		'hidden',
479
		$id
480
	));
481
}
482

    
483
if ($act == "edit")
484
{
485
	$form->addGlobal(new Form_Input(
486
		'refid',
487
		null,
488
		'hidden',
489
		$pconfig['refid']
490
	));
491
}
492

    
493
$section = new Form_Section('Create / edit CA');
494

    
495
$section->addInput(new Form_Input(
496
	'descr',
497
	'Descriptive name',
498
	'text',
499
	$pconfig['descr']
500
));
501

    
502
if (!isset($id) || $act == "edit")
503
{
504
	$section->addInput(new Form_Select(
505
		'method',
506
		'Method',
507
		$pconfig['method'],
508
		$ca_methods
509
	))->toggles();
510
}
511

    
512
$form->add($section);
513

    
514
$section = new Form_Section('Existing Certificate Authority');
515
$section->addClass('toggle-existing collapse');
516

    
517
$section->addInput(new Form_Textarea(
518
	'cert',
519
	'Certificate data',
520
	$pconfig['cert']
521
))->setHelp('Paste a certificate in X.509 PEM format here.');
522

    
523
$section->addInput(new Form_Textarea(
524
	'key',
525
	'Certificate Private Key (optional)',
526
	$pconfig['key']
527
))->setHelp('Paste the private key for the above certificate here. This is '.
528
	'optional in most cases, but required if you need to generate a '.
529
	'Certificate Revocation List (CRL).');
530

    
531
$section->addInput(new Form_Input(
532
	'serial',
533
	'Serial for next certificate',
534
	'number',
535
	$pconfig['serial']
536
))->setHelp('Enter a decimal number to be used as the serial number for the next '.
537
	'certificate to be created using this CA.');
538

    
539
$form->add($section);
540

    
541
$section = new Form_Section('Internal Certificate Authority');
542
$section->addClass('toggle-internal', 'toggle-intermediate', 'collapse');
543

    
544
$allCas = array();
545
foreach ($a_ca as $ca)
546
{
547
	if (!$ca['prv'])
548
			continue;
549

    
550
	$allCas[ $ca['refid'] ] = $ca['descr'];
551
}
552

    
553
$group = new Form_Group('Signing Certificate Authority');
554
$group->addClass('toggle-intermediate', 'collapse');
555
$group->add(new Form_Select(
556
	'caref',
557
	null,
558
	$pconfig['caref'],
559
	$allCas
560
));
561
$section->add($group);
562

    
563
$section->addInput(new Form_Select(
564
	'keylen',
565
	'Key length (bits)',
566
	$pconfig['keylen'],
567
	array_combine($ca_keylens, $ca_keylens)
568
));
569

    
570
$section->addInput(new Form_Select(
571
	'digest_alg',
572
	'Digest Algorithm',
573
	$pconfig['digest_alg'],
574
	array_combine($openssl_digest_algs, $openssl_digest_algs)
575
))->setHelp('NOTE: It is recommended to use an algorithm stronger than SHA1 '.
576
	'when possible.');
577

    
578
$section->addInput(new Form_Input(
579
	'lifetime',
580
	'Lifetime (days)',
581
	'number',
582
	$pconfig['lifetime']
583
));
584

    
585
$section->addInput(new Form_Select(
586
	'dn_country',
587
	'Country Code',
588
	$pconfig['dn_country'],
589
	$dn_cc
590
));
591

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

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

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

    
616
$section->addInput(new Form_Input(
617
	'dn_email',
618
	'Email Address',
619
	'email',
620
	$pconfig['dn_email'],
621
	['placeholder' => 'e.g. admin@mycompany.com']
622
));
623

    
624
$section->addInput(new Form_Input(
625
	'dn_commonname',
626
	'Common Name',
627
	'text',
628
	$pconfig['dn_commonname'],
629
	['placeholder' => 'e.g. internal-ca']
630
));
631

    
632
$form->add($section);
633

    
634
print $form;
635

    
636
$internal_ca_count = 0;
637
foreach ($a_ca as $ca) {
638
	if ($ca['prv']) {
639
		$internal_ca_count++;
640
	}
641
}
642

    
643
include('foot.inc');
(194-194/234)