Project

General

Profile

Download (16.1 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
}
57
if (isset($_POST['id']) && is_numericint($_POST['id'])) {
58
	$id = $_POST['id'];
59
}
60

    
61
if (!is_array($config['ca'])) {
62
	$config['ca'] = array();
63
}
64

    
65
$a_ca =& $config['ca'];
66

    
67
if (!is_array($config['cert'])) {
68
	$config['cert'] = array();
69
}
70

    
71
$a_cert =& $config['cert'];
72

    
73
if (!is_array($config['crl'])) {
74
	$config['crl'] = array();
75
}
76

    
77
$a_crl =& $config['crl'];
78

    
79
$act = $_GET['act'];
80
if ($_POST['act']) {
81
	$act = $_POST['act'];
82
}
83

    
84
if ($act == "del") {
85

    
86
	if (!isset($a_ca[$id])) {
87
		pfSenseHeader("system_camanager.php");
88
		exit;
89
	}
90

    
91
	$index = count($a_cert) - 1;
92
	for (;$index >= 0; $index--) {
93
		if ($a_cert[$index]['caref'] == $a_ca[$id]['refid']) {
94
			unset($a_cert[$index]);
95
		}
96
	}
97

    
98
	$index = count($a_crl) - 1;
99
	for (;$index >= 0; $index--) {
100
		if ($a_crl[$index]['caref'] == $a_ca[$id]['refid']) {
101
			unset($a_crl[$index]);
102
		}
103
	}
104

    
105
	$name = $a_ca[$id]['descr'];
106
	unset($a_ca[$id]);
107
	write_config();
108
	$savemsg = sprintf(gettext("Certificate Authority %s and its CRLs (if any) successfully deleted"), htmlspecialchars($name)) . "<br />";
109
	pfSenseHeader("system_camanager.php");
110
	exit;
111
}
112

    
113
if ($act == "edit") {
114
	if (!$a_ca[$id]) {
115
		pfSenseHeader("system_camanager.php");
116
		exit;
117
	}
118
	$pconfig['descr']  = $a_ca[$id]['descr'];
119
	$pconfig['refid']  = $a_ca[$id]['refid'];
120
	$pconfig['cert']   = base64_decode($a_ca[$id]['crt']);
121
	$pconfig['serial'] = $a_ca[$id]['serial'];
122
	if (!empty($a_ca[$id]['prv'])) {
123
		$pconfig['key'] = base64_decode($a_ca[$id]['prv']);
124
	}
125
}
126

    
127
if ($act == "new") {
128
	$pconfig['method'] = $_GET['method'];
129
	$pconfig['keylen'] = "2048";
130
	$pconfig['digest_alg'] = "sha256";
131
	$pconfig['lifetime'] = "3650";
132
	$pconfig['dn_commonname'] = "internal-ca";
133
}
134

    
135
if ($act == "exp") {
136

    
137
	if (!$a_ca[$id]) {
138
		pfSenseHeader("system_camanager.php");
139
		exit;
140
	}
141

    
142
	$exp_name = urlencode("{$a_ca[$id]['descr']}.crt");
143
	$exp_data = base64_decode($a_ca[$id]['crt']);
144
	$exp_size = strlen($exp_data);
145

    
146
	header("Content-Type: application/octet-stream");
147
	header("Content-Disposition: attachment; filename={$exp_name}");
148
	header("Content-Length: $exp_size");
149
	echo $exp_data;
150
	exit;
151
}
152

    
153
if ($act == "expkey") {
154

    
155
	if (!$a_ca[$id]) {
156
		pfSenseHeader("system_camanager.php");
157
		exit;
158
	}
159

    
160
	$exp_name = urlencode("{$a_ca[$id]['descr']}.key");
161
	$exp_data = base64_decode($a_ca[$id]['prv']);
162
	$exp_size = strlen($exp_data);
163

    
164
	header("Content-Type: application/octet-stream");
165
	header("Content-Disposition: attachment; filename={$exp_name}");
166
	header("Content-Length: $exp_size");
167
	echo $exp_data;
168
	exit;
169
}
170

    
171
if ($_POST) {
172

    
173
	unset($input_errors);
174
	$input_errors = array();
175
	$pconfig = $_POST;
176

    
177
	/* input validation */
178
	if ($pconfig['method'] == "existing") {
179
		$reqdfields = explode(" ", "descr cert");
180
		$reqdfieldsn = array(
181
			gettext("Descriptive name"),
182
			gettext("Certificate data"));
183
		if ($_POST['cert'] && (!strstr($_POST['cert'], "BEGIN CERTIFICATE") || !strstr($_POST['cert'], "END CERTIFICATE"))) {
184
			$input_errors[] = gettext("This certificate does not appear to be valid.");
185
		}
186
		if ($_POST['key'] && strstr($_POST['key'], "ENCRYPTED")) {
187
			$input_errors[] = gettext("Encrypted private keys are not yet supported.");
188
		}
189
	}
190
	if ($pconfig['method'] == "internal") {
191
		$reqdfields = explode(" ",
192
			"descr keylen lifetime dn_country dn_state dn_city ".
193
			"dn_organization dn_email dn_commonname");
194
		$reqdfieldsn = array(
195
			gettext("Descriptive name"),
196
			gettext("Key length"),
197
			gettext("Lifetime"),
198
			gettext("Distinguished name Country Code"),
199
			gettext("Distinguished name State or Province"),
200
			gettext("Distinguished name City"),
201
			gettext("Distinguished name Organization"),
202
			gettext("Distinguished name Email Address"),
203
			gettext("Distinguished name Common Name"));
204
	}
205
	if ($pconfig['method'] == "intermediate") {
206
		$reqdfields = explode(" ",
207
			"descr caref keylen lifetime dn_country dn_state dn_city ".
208
			"dn_organization dn_email dn_commonname");
209
		$reqdfieldsn = array(
210
			gettext("Descriptive name"),
211
			gettext("Signing Certificate Authority"),
212
			gettext("Key length"),
213
			gettext("Lifetime"),
214
			gettext("Distinguished name Country Code"),
215
			gettext("Distinguished name State or Province"),
216
			gettext("Distinguished name City"),
217
			gettext("Distinguished name Organization"),
218
			gettext("Distinguished name Email Address"),
219
			gettext("Distinguished name Common Name"));
220
	}
221

    
222
	do_input_validation($_POST, $reqdfields, $reqdfieldsn, $input_errors);
223
	if ($pconfig['method'] != "existing") {
224
		/* Make sure we do not have invalid characters in the fields for the certificate */
225
		if (preg_match("/[\?\>\<\&\/\\\"\']/", $_POST['descr'])) {
226
			array_push($input_errors, "The field 'Descriptive Name' contains invalid characters.");
227
		}
228

    
229
		for ($i = 0; $i < count($reqdfields); $i++) {
230
			if ($reqdfields[$i] == 'dn_email') {
231
				if (preg_match("/[\!\#\$\%\^\(\)\~\?\>\<\&\/\\\,\"\']/", $_POST["dn_email"])) {
232
					array_push($input_errors, "The field 'Distinguished name Email Address' contains invalid characters.");
233
				}
234
			} else if ($reqdfields[$i] == 'dn_commonname') {
235
				if (preg_match("/[\!\@\#\$\%\^\(\)\~\?\>\<\&\/\\\,\"\']/", $_POST["dn_commonname"])) {
236
					array_push($input_errors, "The field 'Distinguished name Common Name' contains invalid characters.");
237
				}
238
			} else if (($reqdfields[$i] != "descr") && preg_match("/[\!\@\#\$\%\^\(\)\~\?\>\<\&\/\\\,\.\"\']/", $_POST["$reqdfields[$i]"])) {
239
				array_push($input_errors, "The field '" . $reqdfieldsn[$i] . "' contains invalid characters.");
240
			}
241
		}
242
		if (!in_array($_POST["keylen"], $ca_keylens)) {
243
			array_push($input_errors, gettext("Please select a valid Key Length."));
244
		}
245
		if (!in_array($_POST["digest_alg"], $openssl_digest_algs)) {
246
			array_push($input_errors, gettext("Please select a valid Digest Algorithm."));
247
		}
248
	}
249

    
250
	/* if this is an AJAX caller then handle via JSON */
251
	if (isAjax() && is_array($input_errors)) {
252
		input_errors2Ajax($input_errors);
253
		exit;
254
	}
255

    
256
	/* save modifications */
257
	if (!$input_errors) {
258

    
259
		$ca = array();
260
		if (!isset($pconfig['refid']) || empty($pconfig['refid'])) {
261
			$ca['refid'] = uniqid();
262
		} else {
263
			$ca['refid'] = $pconfig['refid'];
264
		}
265

    
266
		if (isset($id) && $a_ca[$id]) {
267
			$ca = $a_ca[$id];
268
		}
269

    
270
		$ca['descr'] = $pconfig['descr'];
271

    
272
		if ($act == "edit") {
273
			$ca['descr']  = $pconfig['descr'];
274
			$ca['refid']  = $pconfig['refid'];
275
			$ca['serial'] = $pconfig['serial'];
276
			$ca['crt']    = base64_encode($pconfig['cert']);
277
			if (!empty($pconfig['key'])) {
278
				$ca['prv']    = base64_encode($pconfig['key']);
279
			}
280
		} else {
281
			$old_err_level = error_reporting(0); /* otherwise openssl_ functions throw warnings directly to a page screwing menu tab */
282
			if ($pconfig['method'] == "existing") {
283
				ca_import($ca, $pconfig['cert'], $pconfig['key'], $pconfig['serial']);
284
			} else if ($pconfig['method'] == "internal") {
285
				$dn = array(
286
					'countryName' => $pconfig['dn_country'],
287
					'stateOrProvinceName' => $pconfig['dn_state'],
288
					'localityName' => $pconfig['dn_city'],
289
					'organizationName' => $pconfig['dn_organization'],
290
					'emailAddress' => $pconfig['dn_email'],
291
					'commonName' => $pconfig['dn_commonname']);
292
				if (!ca_create($ca, $pconfig['keylen'], $pconfig['lifetime'], $dn, $pconfig['digest_alg'])) {
293
					while ($ssl_err = openssl_error_string()) {
294
						$input_errors = array();
295
						array_push($input_errors, "openssl library returns: " . $ssl_err);
296
					}
297
				}
298
			}
299
			else if ($pconfig['method'] == "intermediate") {
300
				$dn = array(
301
					'countryName' => $pconfig['dn_country'],
302
					'stateOrProvinceName' => $pconfig['dn_state'],
303
					'localityName' => $pconfig['dn_city'],
304
					'organizationName' => $pconfig['dn_organization'],
305
					'emailAddress' => $pconfig['dn_email'],
306
					'commonName' => $pconfig['dn_commonname']);
307
				if (!ca_inter_create($ca, $pconfig['keylen'], $pconfig['lifetime'], $dn, $pconfig['caref'], $pconfig['digest_alg'])) {
308
					while ($ssl_err = openssl_error_string()) {
309
						$input_errors = array();
310
						array_push($input_errors, "openssl library returns: " . $ssl_err);
311
					}
312
				}
313
			}
314
			error_reporting($old_err_level);
315
		}
316

    
317
		if (isset($id) && $a_ca[$id]) {
318
			$a_ca[$id] = $ca;
319
		} else {
320
			$a_ca[] = $ca;
321
		}
322

    
323
		if (!$input_errors) {
324
			write_config();
325
		}
326

    
327
//		pfSenseHeader("system_camanager.php");
328
	}
329
}
330

    
331
include("head.inc");
332

    
333
if ($input_errors)
334
	print_input_errors($input_errors);
335
if ($savemsg)
336
	print_info_box($savemsg);
337

    
338
// Load valid country codes
339
$dn_cc = array();
340
if (file_exists("/etc/ca_countries")){
341
	$dn_cc_file=file("/etc/ca_countries");
342
	foreach($dn_cc_file as $line)
343
		if (preg_match('/^(\S*)\s(.*)$/', $line, $matches))
344
			array_push($dn_cc, $matches[1]);
345
}
346

    
347
$tab_array = array();
348
$tab_array[] = array(gettext("CAs"), true, "system_camanager.php");
349
$tab_array[] = array(gettext("Certificates"), false, "system_certmanager.php");
350
$tab_array[] = array(gettext("Certificate Revocation"), false, "system_crlmanager.php");
351
display_top_tabs($tab_array);
352

    
353
if (!($act == "new" || $act == "edit" || $act == gettext("Save") || $input_errors))
354
{
355
?>
356
<div class="table-responsive">
357
<table class="table table-striped table-hover">
358
	<thead>
359
		<tr>
360
			<th><?=gettext("Name")?></th>
361
			<th><?=gettext("Internal")?></th>
362
			<th><?=gettext("Issuer")?></th>
363
			<th><?=gettext("Certificates")?></th>
364
			<th><?=gettext("Distinguished Name")?></th>
365
			<th></th>
366
		</tr>
367
	</thead>
368
	<tbody>
369
<?php
370
foreach ($a_ca as $i => $ca):
371
	$name = htmlspecialchars($ca['descr']);
372
	$subj = cert_get_subject($ca['crt']);
373
	$issuer = cert_get_issuer($ca['crt']);
374
	list($startdate, $enddate) = cert_get_dates($ca['crt']);
375
	if ($subj == $issuer)
376
		$issuer_name = gettext("self-signed");
377
	else
378
		$issuer_name = gettext("external");
379
	$subj = htmlspecialchars($subj);
380
	$issuer = htmlspecialchars($issuer);
381
	$certcount = 0;
382

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

    
387
	// TODO : Need gray certificate icon
388
	$internal = (!!$ca['prv']);
389

    
390
	foreach ($a_cert as $cert)
391
		if ($cert['caref'] == $ca['refid'])
392
			$certcount++;
393

    
394
	foreach ($a_ca as $cert)
395
		if ($cert['caref'] == $ca['refid'])
396
			$certcount++;
397
?>
398
		<tr>
399
			<td><?=$name?></td>
400
			<td><?=$internal?></td>
401
			<td><i><?=$issuer_name?></i></td>
402
			<td><?=$certcount?></td>
403
			<td>
404
				<?=$subj?>
405
				<br />
406
				<small>
407
					<?=gettext("Valid From")?>: <b><?=$startdate ?></b>, <?=gettext("Valid Until")?>: <b><?=$enddate ?></b>
408
				</small>
409
			</td>
410
			<td>
411
				<a href="system_camanager.php?act=edit&amp;id=<?=$i?>" class="btn btn-xs btn-primary">
412
					<?=gettext("edit")?>
413
				</a>
414
				<a href="system_camanager.php?act=exp&amp;id=<?=$i?>" class="btn btn-xs btn-default">
415
					<?=gettext("export cert")?>
416
				</a>
417
				<?php if ($ca['prv']): ?>
418
					<a href="system_camanager.php?act=expkey&amp;id=<?=$i?>" class="btn btn-xs btn-default">
419
						<?=gettext("export private key")?>
420
					</a>
421
				<?php endif?>
422
				<a href="system_camanager.php?act=del&amp;id=<?=$i?>" class="btn btn-xs btn-danger">
423
					<?=gettext("delete")?>
424
				</a>
425
			</td>
426
		</tr>
427
<?php endforeach; ?>
428
	</tbody>
429
</table>
430

    
431
<nav class="action-buttons">
432
	<a href="?act=new" class="btn btn-success">add new</a>
433
</nav>
434
<?
435
	include("foot.inc");
436
	exit;
437
}
438

    
439
require('classes/Form.class.php');
440
$form = new Form;
441
$form->setAction('system_camanager.php?act=edit');
442
if (isset($id) && $a_ca[$id])
443
{
444
	$form->addGlobal(new Form_Input(
445
		'id',
446
		null,
447
		'hidden',
448
		$id
449
	));
450
}
451

    
452
if ($act == "edit")
453
{
454
	$form->addGlobal(new Form_Input(
455
		'refid',
456
		null,
457
		'hidden',
458
		$pconfig['refid']
459
	));
460
}
461

    
462
$section = new Form_Section('Create / edit CA');
463

    
464
$section->addInput(new Form_Input(
465
	'descr',
466
	'Descriptive name',
467
	'text',
468
	$pconfig['descr']
469
));
470

    
471
if (!isset($id) || $act == "edit")
472
{
473
	$section->addInput(new Form_Select(
474
		'method',
475
		'Method',
476
		$pconfig['method'],
477
		$ca_methods
478
	))->toggles();
479
}
480

    
481
$form->add($section);
482

    
483
$section = new Form_Section('Existing Certificate Authority');
484
$section->addClass('toggle-existing collapse');
485

    
486
$section->addInput(new Form_Textarea(
487
	'cert',
488
	'Certificate data',
489
	$pconfig['cert']
490
))->setHelp('Paste a certificate in X.509 PEM format here.');
491

    
492
$section->addInput(new Form_Textarea(
493
	'key',
494
	'Certificate Private Key (optional)',
495
	$pconfig['key']
496
))->setHelp('Paste the private key for the above certificate here. This is '.
497
	'optional in most cases, but required if you need to generate a '.
498
	'Certificate Revocation List (CRL).');
499

    
500
$section->addInput(new Form_Input(
501
	'serial',
502
	'Serial for next certificate',
503
	'number',
504
	$pconfig['serial']
505
))->setHelp('Enter a decimal number to be used as the serial number for the next '.
506
	'certificate to be created using this CA.');
507

    
508
$form->add($section);
509

    
510
$section = new Form_Section('Internal Certificate Authority');
511
$section->addClass('toggle-internal', 'toggle-intermediate', 'collapse');
512

    
513
$allCas = array();
514
foreach ($a_ca as $ca)
515
{
516
	if (!$ca['prv'])
517
			continue;
518

    
519
	$allCas[ $ca['refid'] ] = $ca['descr'];
520
}
521

    
522
$group = new Form_Group('Signing Certificate Authority');
523
$group->addClass('toggle-intermediate');
524
$group->add(new Form_Select(
525
	'caref',
526
	null,
527
	$pconfig['caref'],
528
	$allCas
529
));
530
$section->add($group);
531

    
532
$section->addInput(new Form_Select(
533
	'keylen',
534
	'Key length (bits)',
535
	$pconfig['keylen'],
536
	$ca_keylens
537
));
538

    
539
$section->addInput(new Form_Select(
540
	'digest_alg',
541
	'Digest Algorithm',
542
	$pconfig['digest_alg'],
543
	$openssl_digest_algs
544
))->setHelp('NOTE: It is recommended to use an algorithm stronger than SHA1 '.
545
	'when possible.');
546

    
547
$section->addInput(new Form_Input(
548
	'lifetime',
549
	'Lifetime (days)',
550
	'number',
551
	$pconfig['lifetime']
552
));
553

    
554
$section->addInput(new Form_Select(
555
	'dn_country',
556
	'Country Code',
557
	$pconfig['dn_country'],
558
	$dn_cc
559
));
560

    
561
$section->addInput(new Form_Input(
562
	'dn_state',
563
	'State or Province',
564
	'text',
565
	$pconfig['dn_state'],
566
	['placeholder' => 'e.g. Texas']
567
));
568

    
569
$section->addInput(new Form_Input(
570
	'dn_city',
571
	'City',
572
	'text',
573
	$pconfig['dn_city'],
574
	['placeholder' => 'e.g. Austin']
575
));
576

    
577
$section->addInput(new Form_Input(
578
	'dn_organization',
579
	'Organization',
580
	'text',
581
	$pconfig['dn_organization'],
582
	['placeholder' => 'e.g. My Company Inc.']
583
));
584

    
585
$section->addInput(new Form_Input(
586
	'dn_email',
587
	'Email Address',
588
	'email',
589
	$pconfig['dn_email'],
590
	['placeholder' => 'e.g. admin@mycompany.com']
591
));
592

    
593
$section->addInput(new Form_Input(
594
	'dn_commonname',
595
	'Common Name',
596
	'text',
597
	$pconfig['dn_commonname'],
598
	['placeholder' => 'e.g. internal-ca']
599
));
600

    
601
$form->add($section);
602

    
603
print $form;
604

    
605
include('foot.inc');
(195-195/238)