Project

General

Profile

Download (17.6 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * system_camanager.php
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-2016 Rubicon Communications, LLC (Netgate)
7
 * Copyright (c) 2008 Shrew Soft Inc
8
 * All rights reserved.
9
 *
10
 * Licensed under the Apache License, Version 2.0 (the "License");
11
 * you may not use this file except in compliance with the License.
12
 * You may obtain a copy of the License at
13
 *
14
 * http://www.apache.org/licenses/LICENSE-2.0
15
 *
16
 * Unless required by applicable law or agreed to in writing, software
17
 * distributed under the License is distributed on an "AS IS" BASIS,
18
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
 * See the License for the specific language governing permissions and
20
 * limitations under the License.
21
 */
22

    
23
##|+PRIV
24
##|*IDENT=page-system-camanager
25
##|*NAME=System: CA Manager
26
##|*DESCR=Allow access to the 'System: CA Manager' page.
27
##|*MATCH=system_camanager.php*
28
##|-PRIV
29

    
30
require_once("guiconfig.inc");
31
require_once("certs.inc");
32

    
33
$ca_methods = array(
34
	"existing" => gettext("Import an existing Certificate Authority"),
35
	"internal" => gettext("Create an internal Certificate Authority"),
36
	"intermediate" => gettext("Create an intermediate Certificate Authority"));
37

    
38
$ca_keylens = array("512", "1024", "2048", "3072", "4096", "7680", "8192", "15360", "16384");
39
$openssl_digest_algs = array("sha1", "sha224", "sha256", "sha384", "sha512", "whirlpool");
40

    
41
if (is_numericint($_GET['id'])) {
42
	$id = $_GET['id'];
43
}
44
if (isset($_POST['id']) && is_numericint($_POST['id'])) {
45
	$id = $_POST['id'];
46
}
47

    
48
if (!is_array($config['ca'])) {
49
	$config['ca'] = array();
50
}
51

    
52
$a_ca =& $config['ca'];
53

    
54
if (!is_array($config['cert'])) {
55
	$config['cert'] = array();
56
}
57

    
58
$a_cert =& $config['cert'];
59

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

    
64
$a_crl =& $config['crl'];
65

    
66
$act = $_GET['act'];
67
if ($_POST['act']) {
68
	$act = $_POST['act'];
69
}
70

    
71
if ($act == "del") {
72

    
73
	if (!isset($a_ca[$id])) {
74
		pfSenseHeader("system_camanager.php");
75
		exit;
76
	}
77

    
78
	/* Only remove CA reference when deleting. It can be reconnected if a new matching CA is imported */
79
	$index = count($a_cert) - 1;
80
	for (;$index >= 0; $index--) {
81
		if ($a_cert[$index]['caref'] == $a_ca[$id]['refid']) {
82
			unset($a_cert[$index]['caref']);
83
		}
84
	}
85

    
86
	/* Remove any CRLs for this CA, there is no way to recover the connection once the CA has been removed. */
87
	$index = count($a_crl) - 1;
88
	for (;$index >= 0; $index--) {
89
		if ($a_crl[$index]['caref'] == $a_ca[$id]['refid']) {
90
			unset($a_crl[$index]);
91
		}
92
	}
93

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

    
102
if ($act == "edit") {
103
	if (!$a_ca[$id]) {
104
		pfSenseHeader("system_camanager.php");
105
		exit;
106
	}
107
	$pconfig['descr']  = $a_ca[$id]['descr'];
108
	$pconfig['refid']  = $a_ca[$id]['refid'];
109
	$pconfig['cert']   = base64_decode($a_ca[$id]['crt']);
110
	$pconfig['serial'] = $a_ca[$id]['serial'];
111
	if (!empty($a_ca[$id]['prv'])) {
112
		$pconfig['key'] = base64_decode($a_ca[$id]['prv']);
113
	}
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
		}
175
		if ($_POST['key'] && strstr($_POST['key'], "ENCRYPTED")) {
176
			$input_errors[] = gettext("Encrypted private keys are not yet supported.");
177
		}
178
		if (!$input_errors && !empty($_POST['key']) && cert_get_modulus($_POST['cert'], false) != prv_get_modulus($_POST['key'], false)) {
179
			$input_errors[] = gettext("The submitted private key does not match the submitted certificate data.");
180
		}
181
	}
182
	if ($pconfig['method'] == "internal") {
183
		$reqdfields = explode(" ",
184
			"descr keylen lifetime dn_country dn_state dn_city ".
185
			"dn_organization dn_email dn_commonname");
186
		$reqdfieldsn = array(
187
			gettext("Descriptive name"),
188
			gettext("Key length"),
189
			gettext("Lifetime"),
190
			gettext("Distinguished name Country Code"),
191
			gettext("Distinguished name State or Province"),
192
			gettext("Distinguished name City"),
193
			gettext("Distinguished name Organization"),
194
			gettext("Distinguished name Email Address"),
195
			gettext("Distinguished name Common Name"));
196
	}
197
	if ($pconfig['method'] == "intermediate") {
198
		$reqdfields = explode(" ",
199
			"descr caref keylen lifetime dn_country dn_state dn_city ".
200
			"dn_organization dn_email dn_commonname");
201
		$reqdfieldsn = array(
202
			gettext("Descriptive name"),
203
			gettext("Signing Certificate Authority"),
204
			gettext("Key length"),
205
			gettext("Lifetime"),
206
			gettext("Distinguished name Country Code"),
207
			gettext("Distinguished name State or Province"),
208
			gettext("Distinguished name City"),
209
			gettext("Distinguished name Organization"),
210
			gettext("Distinguished name Email Address"),
211
			gettext("Distinguished name Common Name"));
212
	}
213

    
214
	do_input_validation($_POST, $reqdfields, $reqdfieldsn, $input_errors);
215
	if ($pconfig['method'] != "existing") {
216
		/* Make sure we do not have invalid characters in the fields for the certificate */
217
		if (preg_match("/[\?\>\<\&\/\\\"\']/", $_POST['descr'])) {
218
			array_push($input_errors, gettext("The field 'Descriptive Name' contains invalid characters."));
219
		}
220

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

    
242
	/* save modifications */
243
	if (!$input_errors) {
244
		$ca = array();
245
		if (!isset($pconfig['refid']) || empty($pconfig['refid'])) {
246
			$ca['refid'] = uniqid();
247
		} else {
248
			$ca['refid'] = $pconfig['refid'];
249
		}
250

    
251
		if (isset($id) && $a_ca[$id]) {
252
			$ca = $a_ca[$id];
253
		}
254

    
255
		$ca['descr'] = $pconfig['descr'];
256

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

    
307
		if (isset($id) && $a_ca[$id]) {
308
			$a_ca[$id] = $ca;
309
		} else {
310
			$a_ca[] = $ca;
311
		}
312

    
313
		if (!$input_errors) {
314
			write_config();
315
		}
316

    
317
		pfSenseHeader("system_camanager.php");
318
	}
319
}
320

    
321
$pgtitle = array(gettext("System"), gettext("Certificate Manager"), gettext("CAs"));
322

    
323
if ($act == "new" || $act == "edit" || $act == gettext("Save") || $input_errors) {
324
	$pgtitle[] = gettext('Edit');
325
}
326
include("head.inc");
327

    
328
if ($input_errors) {
329
	print_input_errors($input_errors);
330
}
331

    
332
if ($savemsg) {
333
	print_info_box($savemsg, 'success');
334
}
335

    
336
// Load valid country codes
337
$dn_cc = array();
338
if (file_exists("/etc/ca_countries")) {
339
	$dn_cc_file=file("/etc/ca_countries");
340
	foreach ($dn_cc_file as $line) {
341
		if (preg_match('/^(\S*)\s(.*)$/', $line, $matches)) {
342
			$dn_cc[$matches[1]] = $matches[1];
343
		}
344
	}
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
<div class="panel panel-default">
356
	<div class="panel-heading"><h2 class="panel-title"><?=gettext('Certificate Authorities')?></h2></div>
357
	<div class="panel-body">
358
		<div class="table-responsive">
359
		<table class="table table-striped table-hover table-rowdblclickedit">
360
			<thead>
361
				<tr>
362
					<th><?=gettext("Name")?></th>
363
					<th><?=gettext("Internal")?></th>
364
					<th><?=gettext("Issuer")?></th>
365
					<th><?=gettext("Certificates")?></th>
366
					<th><?=gettext("Distinguished Name")?></th>
367
					<th><?=gettext("In Use")?></th>
368
					<th><?=gettext("Actions")?></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
	}
383
	$subj = htmlspecialchars($subj);
384
	$issuer = htmlspecialchars($issuer);
385
	$certcount = 0;
386

    
387
	$issuer_ca = lookup_ca($ca['caref']);
388
	if ($issuer_ca) {
389
		$issuer_name = $issuer_ca['descr'];
390
	}
391

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

    
398
	foreach ($a_ca as $cert) {
399
		if ($cert['caref'] == $ca['refid']) {
400
			$certcount++;
401
		}
402
	}
403
?>
404
				<tr>
405
					<td><?=$name?></td>
406
					<td><i class="fa fa-<?= (!empty($ca['prv'])) ? "check" : "times" ; ?>"></i></td>
407
					<td><i><?=$issuer_name?></i></td>
408
					<td><?=$certcount?></td>
409
					<td>
410
						<?=$subj?>
411
						<br />
412
						<small>
413
							<?=gettext("Valid From")?>: <b><?=$startdate ?></b><br /><?=gettext("Valid Until")?>: <b><?=$enddate ?></b>
414
						</small>
415
					</td>
416
					<td class="text-nowrap">
417
						<?php if (is_openvpn_server_ca($ca['refid'])): ?>
418
							<?=gettext("OpenVPN Server")?><br/>
419
						<?php endif?>
420
						<?php if (is_openvpn_client_ca($ca['refid'])): ?>
421
							<?=gettext("OpenVPN Client")?><br/>
422
						<?php endif?>
423
						<?php if (is_ipsec_peer_ca($ca['refid'])): ?>
424
							<?=gettext("IPsec Tunnel")?><br/>
425
						<?php endif?>
426
						<?php if (is_ldap_peer_ca($ca['refid'])): ?>
427
							<?=gettext("LDAP Server")?>
428
						<?php endif?>
429
					</td>
430
					<td class="text-nowrap">
431
						<a class="fa fa-pencil"	title="<?=gettext("Edit CA")?>"	href="system_camanager.php?act=edit&amp;id=<?=$i?>"></a>
432
						<a class="fa fa-certificate"	title="<?=gettext("Export CA")?>"	href="system_camanager.php?act=exp&amp;id=<?=$i?>"></a>
433
					<?php if ($ca['prv']): ?>
434
						<a class="fa fa-key"	title="<?=gettext("Export key")?>"	href="system_camanager.php?act=expkey&amp;id=<?=$i?>"></a>
435
					<?php endif?>
436
					<?php if (!ca_in_use($ca['refid'])): ?>
437
						<a class="fa fa-trash" 	title="<?=gettext("Delete CA and its CRLs")?>"	href="system_camanager.php?act=del&amp;id=<?=$i?>"></a>
438
					<?php endif?>
439
					</td>
440
				</tr>
441
<?php endforeach; ?>
442
			</tbody>
443
		</table>
444
		</div>
445
	</div>
446
</div>
447

    
448
<nav class="action-buttons">
449
	<a href="?act=new" class="btn btn-success btn-sm">
450
		<i class="fa fa-plus icon-embed-btn"></i>
451
		<?=gettext("Add")?>
452
	</a>
453
</nav>
454
<?php
455
	include("foot.inc");
456
	exit;
457
}
458

    
459
$form = new Form;
460
//$form->setAction('system_camanager.php?act=edit');
461
if (isset($id) && $a_ca[$id]) {
462
	$form->addGlobal(new Form_Input(
463
		'id',
464
		null,
465
		'hidden',
466
		$id
467
	));
468
}
469

    
470
if ($act == "edit") {
471
	$form->addGlobal(new Form_Input(
472
		'refid',
473
		null,
474
		'hidden',
475
		$pconfig['refid']
476
	));
477
}
478

    
479
$section = new Form_Section('Create / Edit CA');
480

    
481
$section->addInput(new Form_Input(
482
	'descr',
483
	'Descriptive name',
484
	'text',
485
	$pconfig['descr']
486
));
487

    
488
if (!isset($id) || $act == "edit") {
489
	$section->addInput(new Form_Select(
490
		'method',
491
		'Method',
492
		$pconfig['method'],
493
		$ca_methods
494
	))->toggles();
495
}
496

    
497
$form->add($section);
498

    
499
$section = new Form_Section('Existing Certificate Authority');
500
$section->addClass('toggle-existing collapse');
501

    
502
$section->addInput(new Form_Textarea(
503
	'cert',
504
	'Certificate data',
505
	$pconfig['cert']
506
))->setHelp('Paste a certificate in X.509 PEM format here.');
507

    
508
$section->addInput(new Form_Textarea(
509
	'key',
510
	'Certificate Private Key (optional)',
511
	$pconfig['key']
512
))->setHelp('Paste the private key for the above certificate here. This is '.
513
	'optional in most cases, but is required when generating a '.
514
	'Certificate Revocation List (CRL).');
515

    
516
$section->addInput(new Form_Input(
517
	'serial',
518
	'Serial for next certificate',
519
	'number',
520
	$pconfig['serial']
521
))->setHelp('Enter a decimal number to be used as the serial number for the next '.
522
	'certificate to be created using this CA.');
523

    
524
$form->add($section);
525

    
526
$section = new Form_Section('Internal Certificate Authority');
527
$section->addClass('toggle-internal', 'toggle-intermediate', 'collapse');
528

    
529
$allCas = array();
530
foreach ($a_ca as $ca) {
531
	if (!$ca['prv']) {
532
			continue;
533
	}
534

    
535
	$allCas[ $ca['refid'] ] = $ca['descr'];
536
}
537

    
538
$group = new Form_Group('Signing Certificate Authority');
539
$group->addClass('toggle-intermediate', 'collapse');
540
$group->add(new Form_Select(
541
	'caref',
542
	null,
543
	$pconfig['caref'],
544
	$allCas
545
));
546
$section->add($group);
547

    
548
$section->addInput(new Form_Select(
549
	'keylen',
550
	'Key length (bits)',
551
	$pconfig['keylen'],
552
	array_combine($ca_keylens, $ca_keylens)
553
));
554

    
555
$section->addInput(new Form_Select(
556
	'digest_alg',
557
	'Digest Algorithm',
558
	$pconfig['digest_alg'],
559
	array_combine($openssl_digest_algs, $openssl_digest_algs)
560
))->setHelp('NOTE: It is recommended to use an algorithm stronger than SHA1 '.
561
	'when possible.');
562

    
563
$section->addInput(new Form_Input(
564
	'lifetime',
565
	'Lifetime (days)',
566
	'number',
567
	$pconfig['lifetime']
568
));
569

    
570
$section->addInput(new Form_Select(
571
	'dn_country',
572
	'Country Code',
573
	$pconfig['dn_country'],
574
	$dn_cc
575
));
576

    
577
$section->addInput(new Form_Input(
578
	'dn_state',
579
	'State or Province',
580
	'text',
581
	$pconfig['dn_state'],
582
	['placeholder' => 'e.g. Texas']
583
));
584

    
585
$section->addInput(new Form_Input(
586
	'dn_city',
587
	'City',
588
	'text',
589
	$pconfig['dn_city'],
590
	['placeholder' => 'e.g. Austin']
591
));
592

    
593
$section->addInput(new Form_Input(
594
	'dn_organization',
595
	'Organization',
596
	'text',
597
	$pconfig['dn_organization'],
598
	['placeholder' => 'e.g. My Company Inc']
599
));
600

    
601
$section->addInput(new Form_Input(
602
	'dn_organizationalunit',
603
	'Organizational Unit',
604
	'text',
605
	$pconfig['dn_organizationalunit'],
606
	['placeholder' => 'e.g. My Department Name (optional)']
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
?>
(192-192/225)