Project

General

Profile

Download (15.9 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
	system_camanager.php
4

    
5
	Copyright (C) 2008 Shrew Soft Inc.
6
	Copyright (C) 2013-2015 Electric Sheep Fencing, LP
7
	All rights reserved.
8

    
9
	Redistribution and use in source and binary forms, with or without
10
	modification, 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 the
17
	   documentation and/or other materials provided with the distribution.
18

    
19
	THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
20
	INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
21
	AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22
	AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
23
	OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
	POSSIBILITY OF SUCH DAMAGE.
29
*/
30
/*
31
	pfSense_MODULE:	certificate_manager
32
*/
33

    
34
##|+PRIV
35
##|*IDENT=page-system-camanager
36
##|*NAME=System: CA Manager
37
##|*DESCR=Allow access to the 'System: CA Manager' page.
38
##|*MATCH=system_camanager.php*
39
##|-PRIV
40

    
41
require("guiconfig.inc");
42
require_once("certs.inc");
43

    
44
$ca_methods = array(
45
	"existing" => gettext("Import an existing Certificate Authority"),
46
	"internal" => gettext("Create an internal Certificate Authority"),
47
	"intermediate" => gettext("Create an intermediate Certificate Authority"));
48

    
49
$ca_keylens = array( "512", "1024", "2048", "4096");
50
$openssl_digest_algs = array("sha1", "sha224", "sha256", "sha384", "sha512");
51

    
52
$pgtitle = array(gettext("System"), gettext("Certificate Authority Manager"));
53

    
54
if (is_numericint($_GET['id']))
55
	$id = $_GET['id'];
56
if (isset($_POST['id']) && is_numericint($_POST['id']))
57
	$id = $_POST['id'];
58

    
59
if (!is_array($config['ca']))
60
	$config['ca'] = array();
61

    
62
$a_ca =& $config['ca'];
63

    
64
if (!is_array($config['cert']))
65
	$config['cert'] = array();
66

    
67
$a_cert =& $config['cert'];
68

    
69
if (!is_array($config['crl']))
70
	$config['crl'] = array();
71

    
72
$a_crl =& $config['crl'];
73

    
74
$act = $_GET['act'];
75
if ($_POST['act'])
76
	$act = $_POST['act'];
77

    
78
if ($act == "del") {
79

    
80
	if (!isset($a_ca[$id])) {
81
		pfSenseHeader("system_camanager.php");
82
		exit;
83
	}
84

    
85
	$index = count($a_cert) - 1;
86
	for (;$index >=0; $index--)
87
		if ($a_cert[$index]['caref'] == $a_ca[$id]['refid'])
88
			unset($a_cert[$index]);
89

    
90
	$index = count($a_crl) - 1;
91
	for (;$index >=0; $index--)
92
		if ($a_crl[$index]['caref'] == $a_ca[$id]['refid'])
93
			unset($a_crl[$index]);
94

    
95
	$name = $a_ca[$id]['descr'];
96
	unset($a_ca[$id]);
97
	write_config();
98
	$savemsg = sprintf(gettext("Certificate Authority %s and its CRLs (if any) successfully deleted"), $name) . "<br />";
99
	pfSenseHeader("system_camanager.php");
100
	exit;
101
}
102

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

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

    
124
if ($act == "exp") {
125

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

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

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

    
142
if ($act == "expkey") {
143

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

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

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

    
160
if ($_POST) {
161

    
162
	unset($input_errors);
163
	$input_errors = array();
164
	$pconfig = $_POST;
165

    
166
	/* input validation */
167
	if ($pconfig['method'] == "existing") {
168
		$reqdfields = explode(" ", "descr cert");
169
		$reqdfieldsn = array(
170
				gettext("Descriptive name"),
171
				gettext("Certificate data"));
172
		if ($_POST['cert'] && (!strstr($_POST['cert'], "BEGIN CERTIFICATE") || !strstr($_POST['cert'], "END CERTIFICATE")))
173
			$input_errors[] = gettext("This certificate does not appear to be valid.");
174
		if ($_POST['key'] && strstr($_POST['key'], "ENCRYPTED"))
175
			$input_errors[] = gettext("Encrypted private keys are not yet supported.");
176
	}
177
	if ($pconfig['method'] == "internal") {
178
		$reqdfields = explode(" ",
179
				"descr keylen lifetime dn_country dn_state dn_city ".
180
				"dn_organization dn_email dn_commonname");
181
		$reqdfieldsn = array(
182
				gettext("Descriptive name"),
183
				gettext("Key length"),
184
				gettext("Lifetime"),
185
				gettext("Distinguished name Country Code"),
186
				gettext("Distinguished name State or Province"),
187
				gettext("Distinguished name City"),
188
				gettext("Distinguished name Organization"),
189
				gettext("Distinguished name Email Address"),
190
				gettext("Distinguished name Common Name"));
191
	}
192
	if ($pconfig['method'] == "intermediate") {
193
		$reqdfields = explode(" ",
194
				"descr caref keylen lifetime dn_country dn_state dn_city ".
195
				"dn_organization dn_email dn_commonname");
196
		$reqdfieldsn = array(
197
				gettext("Descriptive name"),
198
				gettext("Signing Certificate Authority"),
199
				gettext("Key length"),
200
				gettext("Lifetime"),
201
				gettext("Distinguished name Country Code"),
202
				gettext("Distinguished name State or Province"),
203
				gettext("Distinguished name City"),
204
				gettext("Distinguished name Organization"),
205
				gettext("Distinguished name Email Address"),
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
		for ($i = 0; $i < count($reqdfields); $i++) {
213
			if ($reqdfields[$i] == 'dn_email'){
214
				if (preg_match("/[\!\#\$\%\^\(\)\~\?\>\<\&\/\\\,\"\']/", $_POST["dn_email"]))
215
					array_push($input_errors, "The field 'Distinguished name Email Address' contains invalid characters.");
216
			}else if ($reqdfields[$i] == 'dn_commonname'){
217
				if (preg_match("/[\!\@\#\$\%\^\(\)\~\?\>\<\&\/\\\,\"\']/", $_POST["dn_commonname"]))
218
					array_push($input_errors, "The field 'Distinguished name Common Name' contains invalid characters.");
219
			}else if (($reqdfields[$i] != "descr") && preg_match("/[\!\@\#\$\%\^\(\)\~\?\>\<\&\/\\\,\.\"\']/", $_POST["$reqdfields[$i]"]))
220
				array_push($input_errors, "The field '" . $reqdfieldsn[$i] . "' contains invalid characters.");
221
		}
222
		if (!in_array($_POST["keylen"], $ca_keylens))
223
			array_push($input_errors, gettext("Please select a valid Key Length."));
224
		if (!in_array($_POST["digest_alg"], $openssl_digest_algs))
225
			array_push($input_errors, gettext("Please select a valid Digest Algorithm."));
226
	}
227

    
228
	/* if this is an AJAX caller then handle via JSON */
229
	if (isAjax() && is_array($input_errors)) {
230
		input_errors2Ajax($input_errors);
231
		exit;
232
	}
233

    
234
	/* save modifications */
235
	if (!$input_errors) {
236

    
237
		$ca = array();
238
		if (!isset($pconfig['refid']) || empty($pconfig['refid']))
239
			$ca['refid'] = uniqid();
240
		else
241
			$ca['refid'] = $pconfig['refid'];
242

    
243
		if (isset($id) && $a_ca[$id])
244
			$ca = $a_ca[$id];
245

    
246
		$ca['descr'] = $pconfig['descr'];
247

    
248
		if ($act == "edit") {
249
			$ca['descr']  = $pconfig['descr'];
250
			$ca['refid']  = $pconfig['refid'];
251
			$ca['serial'] = $pconfig['serial'];
252
			$ca['crt'] = base64_encode($pconfig['cert']);
253
			if (!empty($pconfig['key']))
254
				$ca['prv'] = base64_encode($pconfig['key']);
255
		} else {
256
			$old_err_level = error_reporting(0); /* otherwise openssl_ functions throw warnings directly to a page screwing menu tab */
257
			if ($pconfig['method'] == "existing")
258
				ca_import($ca, $pconfig['cert'], $pconfig['key'], $pconfig['serial']);
259

    
260
			else if ($pconfig['method'] == "internal") {
261
				$dn = array(
262
					'countryName' => $pconfig['dn_country'],
263
					'stateOrProvinceName' => $pconfig['dn_state'],
264
					'localityName' => $pconfig['dn_city'],
265
					'organizationName' => $pconfig['dn_organization'],
266
					'emailAddress' => $pconfig['dn_email'],
267
					'commonName' => $pconfig['dn_commonname']);
268
				if (!ca_create($ca, $pconfig['keylen'], $pconfig['lifetime'], $dn, $pconfig['digest_alg'])){
269
					while($ssl_err = openssl_error_string()){
270
						$input_errors = array();
271
						array_push($input_errors, "openssl library returns: " . $ssl_err);
272
					}
273
				}
274
			}
275
			else if ($pconfig['method'] == "intermediate") {
276
				$dn = array(
277
					'countryName' => $pconfig['dn_country'],
278
					'stateOrProvinceName' => $pconfig['dn_state'],
279
					'localityName' => $pconfig['dn_city'],
280
					'organizationName' => $pconfig['dn_organization'],
281
					'emailAddress' => $pconfig['dn_email'],
282
					'commonName' => $pconfig['dn_commonname']);
283
				if (!ca_inter_create($ca, $pconfig['keylen'], $pconfig['lifetime'], $dn, $pconfig['caref'], $pconfig['digest_alg'])){
284
					while($ssl_err = openssl_error_string()){
285
						$input_errors = array();
286
						array_push($input_errors, "openssl library returns: " . $ssl_err);
287
					}
288
				}
289
			}
290
			error_reporting($old_err_level);
291
		}
292

    
293
		if (isset($id) && $a_ca[$id])
294
			$a_ca[$id] = $ca;
295
		else
296
			$a_ca[] = $ca;
297

    
298
		if (!$input_errors)
299
			write_config();
300

    
301
//		pfSenseHeader("system_camanager.php");
302
	}
303
}
304

    
305
include("head.inc");
306

    
307
if ($input_errors)
308
	print_input_errors($input_errors);
309
if ($savemsg)
310
	print_info_box($savemsg);
311

    
312
// Load valid country codes
313
$dn_cc = array();
314
if (file_exists("/etc/ca_countries")){
315
	$dn_cc_file=file("/etc/ca_countries");
316
	foreach($dn_cc_file as $line)
317
		if (preg_match('/^(\S*)\s(.*)$/', $line, $matches))
318
			array_push($dn_cc, $matches[1]);
319
}
320

    
321
$tab_array = array();
322
$tab_array[] = array(gettext("CAs"), true, "system_camanager.php");
323
$tab_array[] = array(gettext("Certificates"), false, "system_certmanager.php");
324
$tab_array[] = array(gettext("Certificate Revocation"), false, "system_crlmanager.php");
325
display_top_tabs($tab_array);
326

    
327
if (!($act == "new" || $act == "edit" || $act == gettext("Save") || $input_errors))
328
{
329
?>
330
<div class="table-responsive">
331
<table class="table table-striped table-hover">
332
	<thead>
333
		<tr>
334
			<th><?=gettext("Name")?></th>
335
			<th><?=gettext("Internal")?></th>
336
			<th><?=gettext("Issuer")?></th>
337
			<th><?=gettext("Certificates")?></th>
338
			<th><?=gettext("Distinguished Name")?></th>
339
			<th></th>
340
		</tr>
341
	</thead>
342
	<tbody>
343
<?php
344
foreach ($a_ca as $i => $ca):
345
	$name = htmlspecialchars($ca['descr']);
346
	$subj = cert_get_subject($ca['crt']);
347
	$issuer = cert_get_issuer($ca['crt']);
348
	list($startdate, $enddate) = cert_get_dates($ca['crt']);
349
	if($subj == $issuer)
350
		$issuer_name = "<em>" . gettext("self-signed") . "</em>";
351
	else
352
		$issuer_name = "<em>" . gettext("external") . "</em>";
353
	$subj = htmlspecialchars($subj);
354
	$issuer = htmlspecialchars($issuer);
355
	$certcount = 0;
356

    
357
	$issuer_ca = lookup_ca($ca['caref']);
358
	if ($issuer_ca)
359
		$issuer_name = $issuer_ca['descr'];
360

    
361
	// TODO : Need gray certificate icon
362
	$internal = (!!$ca['prv']);
363

    
364
	foreach ($a_cert as $cert)
365
		if ($cert['caref'] == $ca['refid'])
366
			$certcount++;
367

    
368
	foreach ($a_ca as $cert)
369
		if ($cert['caref'] == $ca['refid'])
370
			$certcount++;
371
?>
372
		<tr>
373
			<td><?=$name?></td>
374
			<td><?=$internal?></td>
375
			<td><?=$issuer_name?></td>
376
			<td><?=$certcount?></td>
377
			<td><?=$subj?><br />
378
				<table>
379
					<tr>
380
						<td>&nbsp;</td>
381
						<td><?=gettext("Valid From")?>:</td>
382
						<td><?=$startdate ?></td>
383
					</tr>
384
					<tr>
385
						<td>&nbsp;</td>
386
						<td><?=gettext("Valid Until")?>:</td>
387
						<td><?=$enddate ?></td>
388
					</tr>
389
				</table>
390
			</td>
391
			<td>
392
				<a href="system_camanager.php?act=edit&amp;id=<?=$i?>" class="btn btn-xs btn-primary">
393
					<?=gettext("edit")?>
394
				</a>
395
				<a href="system_camanager.php?act=exp&amp;id=<?=$i?>" class="btn btn-xs btn-default">
396
					<?=gettext("export cert")?>
397
				</a>
398
				<?php if ($ca['prv']): ?>
399
					<a href="system_camanager.php?act=expkey&amp;id=<?=$i?>" class="btn btn-xs btn-default">
400
						<?=gettext("export private key")?>
401
					</a>
402
				<?php endif?>
403
				<a href="system_camanager.php?act=del&amp;id=<?=$i?>" class="btn btn-xs btn-danger">
404
					<?=gettext("delete")?>
405
				</a>
406
			</td>
407
		</tr>
408
<?php endforeach; ?>
409
	</tbody>
410
</table>
411

    
412
<nav class="action-buttons">
413
	<a href="?act=new" class="btn btn-success">add new</a>
414
</nav>
415
<?
416
	include("foot.inc");
417
	exit;
418
}
419

    
420
require('classes/Form.class.php');
421
$form = new Form;
422
$form->setAction('system_camanager.php?act=edit');
423
if (isset($id) && $a_ca[$id])
424
{
425
	$form->addGlobal(new Form_Input(
426
		'id',
427
		null,
428
		'hidden',
429
		$id
430
	));
431
}
432

    
433
if ($act == "edit")
434
{
435
	$form->addGlobal(new Form_Input(
436
		'refid',
437
		null,
438
		'hidden',
439
		$pconfig['refid']
440
	));
441
}
442

    
443
$section = new Form_Section('Create / edit CA');
444

    
445
$section->addInput(new Form_Input(
446
	'descr',
447
	'Descriptive name',
448
	'text',
449
	$pconfig['descr']
450
));
451

    
452
if (!isset($id) || $act == "edit")
453
{
454
	$section->addInput(new Form_Select(
455
		'method',
456
		'Method',
457
		$pconfig['method'],
458
		$ca_methods
459
	))->toggles(null);
460
}
461

    
462
$form->add($section);
463

    
464
$section = new Form_Section('Existing Certificate Authority');
465
$section->addClass('toggle-existing collapse');
466

    
467
$section->addInput(new Form_Textarea(
468
	'cert',
469
	'Certificate data',
470
	$pconfig['cert']
471
))->setHelp('Paste a certificate in X.509 PEM format here.');
472

    
473
$section->addInput(new Form_Textarea(
474
	'key',
475
	'Certificate Private Key (optional)',
476
	$pconfig['key']
477
))->setHelp('Paste the private key for the above certificate here. This is '.
478
	'optional in most cases, but required if you need to generate a '.
479
	'Certificate Revocation List (CRL).');
480

    
481
$section->addInput(new Form_Input(
482
	'serial',
483
	'Serial for next certificate',
484
	'number',
485
	$pconfig['serial']
486
))->setHelp('Enter a decimal number to be used as the serial number for the next '.
487
	'certificate to be created using this CA.');
488

    
489
$form->add($section);
490

    
491
$section = new Form_Section('Internal Certificate Authority');
492
$section->addClass('toggle-internal', 'toggle-intermediate', 'collapse');
493

    
494
$allCas = array();
495
foreach ($a_ca as $ca)
496
{
497
	if (!$ca['prv'])
498
			continue;
499

    
500
	$allCas[ $ca['refid'] ] = $ca['descr'];
501
}
502

    
503
$group = new Form_Group('Signing Certificate Authority');
504
$group->addClass('toggle-intermediate');
505
$group->add(new Form_Select(
506
	'caref',
507
	null,
508
	$pconfig['caref'],
509
	$allCas
510
));
511
$section->add($group);
512

    
513
$section->addInput(new Form_Select(
514
	'keylen',
515
	'Key length (bits)',
516
	$pconfig['keylen'],
517
	$ca_keylens
518
));
519

    
520
$section->addInput(new Form_Select(
521
	'digest_alg',
522
	'Digest Algorithm',
523
	$pconfig['digest_alg'],
524
	$openssl_digest_algs
525
))->setHelp('NOTE: It is recommended to use an algorithm stronger than SHA1 '.
526
	'when possible.');
527

    
528
$section->addInput(new Form_Input(
529
	'lifetime',
530
	'Lifetime (days)',
531
	'number',
532
	$pconfig['lifetime']
533
));
534

    
535
$section->addInput(new Form_Select(
536
	'dn_country',
537
	'Country Code',
538
	$pconfig['dn_country'],
539
	$dn_cc
540
));
541

    
542
$section->addInput(new Form_Input(
543
	'dn_state',
544
	'State or Province',
545
	'text',
546
	$pconfig['dn_state'],
547
	['placeholder' => 'e.g. Texas']
548
));
549

    
550
$section->addInput(new Form_Input(
551
	'dn_city',
552
	'City',
553
	'text',
554
	$pconfig['dn_city'],
555
	['placeholder' => 'e.g. Austin']
556
));
557

    
558
$section->addInput(new Form_Input(
559
	'dn_organization',
560
	'Organization',
561
	'text',
562
	$pconfig['dn_organization'],
563
	['placeholder' => 'e.g. My Company Inc.']
564
));
565

    
566
$section->addInput(new Form_Input(
567
	'dn_email',
568
	'Email Address',
569
	'email',
570
	$pconfig['dn_email'],
571
	['placeholder' => 'e.g. admin@mycompany.com']
572
));
573

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

    
582
$form->add($section);
583

    
584
print $form;
585

    
586
include('foot.inc');
(209-209/252)