Project

General

Profile

Download (17.7 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 (isset($_POST['id']) && is_numericint($_POST['id'])) {
42
	$id = $_POST['id'];
43
}
44

    
45
if (!is_array($config['ca'])) {
46
	$config['ca'] = array();
47
}
48

    
49
$a_ca =& $config['ca'];
50

    
51
if (!is_array($config['cert'])) {
52
	$config['cert'] = array();
53
}
54

    
55
$a_cert =& $config['cert'];
56

    
57
if (!is_array($config['crl'])) {
58
	$config['crl'] = array();
59
}
60

    
61
$a_crl =& $config['crl'];
62

    
63
if ($_POST['act']) {
64
	$act = $_POST['act'];
65
}
66

    
67
if ($act == "del") {
68

    
69
	if (!isset($a_ca[$id])) {
70
		pfSenseHeader("system_camanager.php");
71
		exit;
72
	}
73

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

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

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

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

    
112
if ($act == "new") {
113
	$pconfig['method'] = $_POST['method'];
114
	$pconfig['keylen'] = "2048";
115
	$pconfig['digest_alg'] = "sha256";
116
	$pconfig['lifetime'] = "3650";
117
	$pconfig['dn_commonname'] = "internal-ca";
118
}
119

    
120
if ($act == "exp") {
121

    
122
	if (!$a_ca[$id]) {
123
		pfSenseHeader("system_camanager.php");
124
		exit;
125
	}
126

    
127
	$exp_name = urlencode("{$a_ca[$id]['descr']}.crt");
128
	$exp_data = base64_decode($a_ca[$id]['crt']);
129
	$exp_size = strlen($exp_data);
130

    
131
	header("Content-Type: application/octet-stream");
132
	header("Content-Disposition: attachment; filename={$exp_name}");
133
	header("Content-Length: $exp_size");
134
	echo $exp_data;
135
	exit;
136
}
137

    
138
if ($act == "expkey") {
139

    
140
	if (!$a_ca[$id]) {
141
		pfSenseHeader("system_camanager.php");
142
		exit;
143
	}
144

    
145
	$exp_name = urlencode("{$a_ca[$id]['descr']}.key");
146
	$exp_data = base64_decode($a_ca[$id]['prv']);
147
	$exp_size = strlen($exp_data);
148

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

    
156
if ($_POST && ($_POST['save'] == 'Save')) {
157

    
158
	unset($input_errors);
159
	$input_errors = array();
160
	$pconfig = $_POST;
161

    
162
	/* input validation */
163
	if ($pconfig['method'] == "existing") {
164
		$reqdfields = explode(" ", "descr cert");
165
		$reqdfieldsn = array(
166
			gettext("Descriptive name"),
167
			gettext("Certificate data"));
168
		if ($_POST['cert'] && (!strstr($_POST['cert'], "BEGIN CERTIFICATE") || !strstr($_POST['cert'], "END CERTIFICATE"))) {
169
			$input_errors[] = gettext("This certificate does not appear to be valid.");
170
		}
171
		if ($_POST['key'] && strstr($_POST['key'], "ENCRYPTED")) {
172
			$input_errors[] = gettext("Encrypted private keys are not yet supported.");
173
		}
174
		if (!$input_errors && !empty($_POST['key']) && cert_get_modulus($_POST['cert'], false) != prv_get_modulus($_POST['key'], false)) {
175
			$input_errors[] = gettext("The submitted private key does not match the submitted certificate data.");
176
		}
177
	}
178
	if ($pconfig['method'] == "internal") {
179
		$reqdfields = explode(" ",
180
			"descr keylen lifetime dn_country dn_state dn_city ".
181
			"dn_organization dn_email dn_commonname");
182
		$reqdfieldsn = array(
183
			gettext("Descriptive name"),
184
			gettext("Key length"),
185
			gettext("Lifetime"),
186
			gettext("Distinguished name Country Code"),
187
			gettext("Distinguished name State or Province"),
188
			gettext("Distinguished name City"),
189
			gettext("Distinguished name Organization"),
190
			gettext("Distinguished name Email Address"),
191
			gettext("Distinguished name Common Name"));
192
	}
193
	if ($pconfig['method'] == "intermediate") {
194
		$reqdfields = explode(" ",
195
			"descr caref keylen lifetime dn_country dn_state dn_city ".
196
			"dn_organization dn_email dn_commonname");
197
		$reqdfieldsn = array(
198
			gettext("Descriptive name"),
199
			gettext("Signing Certificate Authority"),
200
			gettext("Key length"),
201
			gettext("Lifetime"),
202
			gettext("Distinguished name Country Code"),
203
			gettext("Distinguished name State or Province"),
204
			gettext("Distinguished name City"),
205
			gettext("Distinguished name Organization"),
206
			gettext("Distinguished name Email Address"),
207
			gettext("Distinguished name Common Name"));
208
	}
209

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

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

    
238
	/* save modifications */
239
	if (!$input_errors) {
240
		$ca = array();
241
		if (!isset($pconfig['refid']) || empty($pconfig['refid'])) {
242
			$ca['refid'] = uniqid();
243
		} else {
244
			$ca['refid'] = $pconfig['refid'];
245
		}
246

    
247
		if (isset($id) && $a_ca[$id]) {
248
			$ca = $a_ca[$id];
249
		}
250

    
251
		$ca['descr'] = $pconfig['descr'];
252

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

    
303
		if (isset($id) && $a_ca[$id]) {
304
			$a_ca[$id] = $ca;
305
		} else {
306
			$a_ca[] = $ca;
307
		}
308

    
309
		if (!$input_errors) {
310
			write_config();
311
		}
312

    
313
		pfSenseHeader("system_camanager.php");
314
	}
315
}
316

    
317
$pgtitle = array(gettext("System"), gettext("Certificate Manager"), gettext("CAs"));
318
$pglinks = array("", "system_camanager.php", "system_camanager.php");
319

    
320
if ($act == "new" || $act == "edit" || $act == gettext("Save") || $input_errors) {
321
	$pgtitle[] = gettext('Edit');
322
	$pglinks[] = "@self";
323
}
324
include("head.inc");
325

    
326
if ($input_errors) {
327
	print_input_errors($input_errors);
328
}
329

    
330
if ($savemsg) {
331
	print_info_box($savemsg, 'success');
332
}
333

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

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

    
351
if (!($act == "new" || $act == "edit" || $act == gettext("Save") || $input_errors)) {
352
?>
353
<div class="panel panel-default">
354
	<div class="panel-heading"><h2 class="panel-title"><?=gettext('Certificate Authorities')?></h2></div>
355
	<div class="panel-body">
356
		<div class="table-responsive">
357
		<table class="table table-striped table-hover table-rowdblclickedit">
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><?=gettext("In Use")?></th>
366
					<th><?=gettext("Actions")?></th>
367
				</tr>
368
			</thead>
369
			<tbody>
370
<?php
371
foreach ($a_ca as $i => $ca):
372
	$name = htmlspecialchars($ca['descr']);
373
	$subj = cert_get_subject($ca['crt']);
374
	$issuer = cert_get_issuer($ca['crt']);
375
	list($startdate, $enddate) = cert_get_dates($ca['crt']);
376
	if ($subj == $issuer) {
377
		$issuer_name = gettext("self-signed");
378
	} else {
379
		$issuer_name = gettext("external");
380
	}
381
	$subj = htmlspecialchars($subj);
382
	$issuer = htmlspecialchars($issuer);
383
	$certcount = 0;
384

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

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

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

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

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

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

    
477
$section = new Form_Section('Create / Edit CA');
478

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

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

    
495
$form->add($section);
496

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

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

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

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

    
522
$form->add($section);
523

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

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

    
533
	$allCas[ $ca['refid'] ] = $ca['descr'];
534
}
535

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

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

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

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

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

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

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

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

    
599
$section->addInput(new Form_Input(
600
	'dn_organizationalunit',
601
	'Organizational Unit',
602
	'text',
603
	$pconfig['dn_organizationalunit'],
604
	['placeholder' => 'e.g. My Department Name (optional)']
605
));
606

    
607
$section->addInput(new Form_Input(
608
	'dn_email',
609
	'*Email Address',
610
	'email',
611
	$pconfig['dn_email'],
612
	['placeholder' => 'e.g. admin@mycompany.com']
613
));
614

    
615
$section->addInput(new Form_Input(
616
	'dn_commonname',
617
	'*Common Name',
618
	'text',
619
	$pconfig['dn_commonname'],
620
	['placeholder' => 'e.g. internal-ca']
621
));
622

    
623
$form->add($section);
624

    
625
print $form;
626

    
627
$internal_ca_count = 0;
628
foreach ($a_ca as $ca) {
629
	if ($ca['prv']) {
630
		$internal_ca_count++;
631
	}
632
}
633

    
634
include('foot.inc');
635
?>
(190-190/223)