Project

General

Profile

Download (16.3 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
		$ca = array();
259
		if (!isset($pconfig['refid']) || empty($pconfig['refid'])) {
260
			$ca['refid'] = uniqid();
261
		} else {
262
			$ca['refid'] = $pconfig['refid'];
263
		}
264

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

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

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

    
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

    
336
if ($savemsg)
337
	print_info_box($savemsg, 'success');
338

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

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

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

    
386
	$issuer_ca = lookup_ca($ca['caref']);
387
	if ($issuer_ca)
388
		$issuer_name = $issuer_ca['descr'];
389

    
390
	// TODO : Need gray certificate icon
391
	$internal = (!!$ca['prv']);
392

    
393
	foreach ($a_cert as $cert)
394
		if ($cert['caref'] == $ca['refid'])
395
			$certcount++;
396

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

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

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

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

    
465
$section = new Form_Section('Create / edit CA');
466

    
467
$section->addInput(new Form_Input(
468
	'descr',
469
	'Descriptive name',
470
	'text',
471
	$pconfig['descr']
472
));
473

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

    
484
$form->add($section);
485

    
486
$section = new Form_Section('Existing Certificate Authority');
487
$section->addClass('toggle-existing collapse');
488

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

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

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

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

    
513
$section = new Form_Section('Internal Certificate Authority');
514
$section->addClass('toggle-internal', 'toggle-intermediate', 'collapse');
515

    
516
$allCas = array();
517
foreach ($a_ca as $ca)
518
{
519
	if (!$ca['prv'])
520
			continue;
521

    
522
	$allCas[ $ca['refid'] ] = $ca['descr'];
523
}
524

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

    
535
$section->addInput(new Form_Select(
536
	'keylen',
537
	'Key length (bits)',
538
	$pconfig['keylen'],
539
	array_combine($ca_keylens, $ca_keylens)
540
));
541

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

    
550
$section->addInput(new Form_Input(
551
	'lifetime',
552
	'Lifetime (days)',
553
	'number',
554
	$pconfig['lifetime']
555
));
556

    
557
$section->addInput(new Form_Select(
558
	'dn_country',
559
	'Country Code',
560
	$pconfig['dn_country'],
561
	$dn_cc
562
));
563

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

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

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

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

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

    
604
$form->add($section);
605

    
606
print $form;
607

    
608
$internal_ca_count = 0;
609
foreach ($a_ca as $ca) {
610
	if ($ca['prv']) {
611
		$internal_ca_count++;
612
	}
613
}
614

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