Project

General

Profile

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

    
25
##|+PRIV
26
##|*IDENT=page-system-certmanager
27
##|*NAME=System: Certificate Manager
28
##|*DESCR=Allow access to the 'System: Certificate Manager' page.
29
##|*MATCH=system_certmanager.php*
30
##|-PRIV
31

    
32
require_once("guiconfig.inc");
33
require_once("certs.inc");
34
require_once("pfsense-utils.inc");
35

    
36
$cert_methods = array(
37
	"internal" => gettext("Create an internal Certificate"),
38
	"import" => gettext("Import an existing Certificate"),
39
	"external" => gettext("Create a Certificate Signing Request"),
40
	"sign" => gettext("Sign a Certificate Signing Request")
41
);
42

    
43
$cert_keylens = array("1024", "2048", "3072", "4096", "6144", "7680", "8192", "15360", "16384");
44
$cert_keytypes = array("RSA", "ECDSA");
45
$cert_types = array(
46
	"server" => "Server Certificate",
47
	"user" => "User Certificate");
48

    
49
global $cert_altname_types;
50
global $openssl_digest_algs;
51
global $cert_strict_values;
52
$max_lifetime = cert_get_max_lifetime();
53
$default_lifetime = min(3650, $max_lifetime);
54
$openssl_ecnames = cert_build_curve_list();
55
$class = "success";
56

    
57
if (isset($_REQUEST['userid']) && is_numericint($_REQUEST['userid'])) {
58
	$userid = $_REQUEST['userid'];
59
}
60

    
61
if (isset($userid)) {
62
	$cert_methods["existing"] = gettext("Choose an existing certificate");
63
	init_config_arr(array('system', 'user'));
64
	$a_user =& $config['system']['user'];
65
}
66

    
67
init_config_arr(array('ca'));
68
$a_ca = &$config['ca'];
69

    
70
init_config_arr(array('cert'));
71
$a_cert = &$config['cert'];
72

    
73
$internal_ca_count = 0;
74
foreach ($a_ca as $ca) {
75
	if ($ca['prv']) {
76
		$internal_ca_count++;
77
	}
78
}
79

    
80
if ($_REQUEST['exportp12']) {
81
	$act = 'p12';
82
} elseif ($_REQUEST['exportpkey']) {
83
	$act = 'key';
84
} else {
85
	$act = $_REQUEST['act'];
86
}
87

    
88
if ($act == 'edit') {
89
	$cert_methods = array(
90
		'edit' => gettext("Edit an existing certificate")
91
	);
92
}
93

    
94
if (isset($_REQUEST['id']) && ctype_alnum($_REQUEST['id'])) {
95
	$id = $_REQUEST['id'];
96
}
97
if (!empty($id)) {
98
	$thiscert =& lookup_cert($id);
99
}
100

    
101
/* Actions other than 'new' require an ID.
102
 * 'del' action must be submitted via POST. */
103
if ((!empty($act) &&
104
    ($act != 'new') &&
105
    !$thiscert) ||
106
    (($act == 'del') && empty($_POST))) {
107
	pfSenseHeader("system_certmanager.php");
108
	exit;
109
}
110

    
111
switch ($act) {
112
	case 'del':
113
		$name = htmlspecialchars($thiscert['descr']);
114
		if (cert_in_use($id)) {
115
			$savemsg = sprintf(gettext("Certificate %s is in use and cannot be deleted"), $name);
116
			$class = "danger";
117
		} else {
118
			foreach ($a_cert as $cid => $acrt) {
119
				if ($acrt['refid'] == $thiscert['refid']) {
120
					unset($a_cert[$cid]);
121
				}
122
			}
123
			$savemsg = sprintf(gettext("Deleted certificate %s"), $name);
124
			write_config($savemsg);
125
		}
126
		unset($act);
127
		break;
128
	case 'new':
129
		/* New certificate, so set default values */
130
		$pconfig['method'] = $_POST['method'];
131
		$pconfig['keytype'] = "RSA";
132
		$pconfig['keylen'] = "2048";
133
		$pconfig['ecname'] = "prime256v1";
134
		$pconfig['digest_alg'] = "sha256";
135
		$pconfig['csr_keytype'] = "RSA";
136
		$pconfig['csr_keylen'] = "2048";
137
		$pconfig['csr_ecname'] = "prime256v1";
138
		$pconfig['csr_digest_alg'] = "sha256";
139
		$pconfig['csrsign_digest_alg'] = "sha256";
140
		$pconfig['type'] = "user";
141
		$pconfig['lifetime'] = $default_lifetime;
142
		break;
143
	case 'edit':
144
		/* Editing a certificate, so populate values */
145
		$pconfig['descr'] = $thiscert['descr'];
146
		$pconfig['cert'] = base64_decode($thiscert['crt']);
147
		$pconfig['key'] = base64_decode($thiscert['prv']);
148
		break;
149
	case 'csr':
150
		/* Editing a CSR, so populate values */
151
		$pconfig['descr'] = $thiscert['descr'];
152
		$pconfig['csr'] = base64_decode($thiscert['csr']);
153
		break;
154
	case 'exp':
155
		/* Exporting a certificate */
156
		send_user_download('data', base64_decode($thiscert['crt']), "{$thiscert['descr']}.crt");
157
		break;
158
	case 'req':
159
		/* Exporting a certificate signing request */
160
		send_user_download('data', base64_decode($thiscert['csr']), "{$thiscert['descr']}.req");
161
		break;
162
	case 'key':
163
		/* Exporting a private key */
164
		$keyout = base64_decode($thiscert['prv']);
165
		if (isset($_POST['exportpass']) && !empty($_POST['exportpass'])) {
166
			$res_key = openssl_pkey_get_private($keyout);
167
			if ($res_key) {
168
				openssl_pkey_export($res_key, $keyout, $_POST['exportpass']);
169
			} else {
170
				$savemsg = gettext("Unable to export password-protected private key.");
171
				$class = 'danger';
172
			}
173
		}
174
		if (!empty($keyout)) {
175
			send_user_download('data', $keyout, "{$thiscert['descr']}.key");
176
		}
177
		break;
178
	case 'p12':
179
		/* Exporting a PKCS#12 file containing the certificate, key, and (if present) CA */
180
		$password = (isset($_POST['exportpass']) && !empty($_POST['exportpass'])) ? $_POST['exportpass'] : null;
181
		$args = array();
182
		$args['friendly_name'] = $thiscert['descr'];
183
		$ca = lookup_ca($thiscert['caref']);
184
		if ($ca) {
185
			/* If the CA can be found, then add the CA to the container */
186
			$args['extracerts'] = openssl_x509_read(base64_decode($ca['crt']));
187
		}
188
		$res_crt = openssl_x509_read(base64_decode($thiscert['crt']));
189
		$res_key = openssl_pkey_get_private(base64_decode($thiscert['prv']));
190
		$exp_data = "";
191
		openssl_pkcs12_export($res_crt, $exp_data, $res_key, $password, $args);
192
		send_user_download('data', $exp_data, "{$thiscert['descr']}.p12");
193
		break;
194
	default:
195
		break;
196
}
197

    
198
if ($_POST['save'] == gettext("Save")) {
199
	/* Creating a new entry */
200
	$input_errors = array();
201
	$pconfig = $_POST;
202

    
203
	switch ($pconfig['method']) {
204
		case 'sign':
205
			$reqdfields = explode(" ",
206
				"descr catosignwith");
207
			$reqdfieldsn = array(
208
				gettext("Descriptive name"),
209
				gettext("CA to sign with"));
210

    
211
			if (($_POST['csrtosign'] === "new") &&
212
			    ((!strstr($_POST['csrpaste'], "BEGIN CERTIFICATE REQUEST") || !strstr($_POST['csrpaste'], "END CERTIFICATE REQUEST")) &&
213
			    (!strstr($_POST['csrpaste'], "BEGIN NEW CERTIFICATE REQUEST") || !strstr($_POST['csrpaste'], "END NEW CERTIFICATE REQUEST")))) {
214
				$input_errors[] = gettext("This signing request does not appear to be valid.");
215
			}
216

    
217
			if ( (($_POST['csrtosign'] === "new") && (strlen($_POST['keypaste']) > 0)) && 
218
			    ((!strstr($_POST['keypaste'], "BEGIN PRIVATE KEY") && !strstr($_POST['keypaste'], "BEGIN EC PRIVATE KEY")) || 
219
			    (strstr($_POST['keypaste'], "BEGIN PRIVATE KEY") && !strstr($_POST['keypaste'], "END PRIVATE KEY")) ||
220
			    (strstr($_POST['keypaste'], "BEGIN EC PRIVATE KEY") && !strstr($_POST['keypaste'], "END EC PRIVATE KEY")))) {
221
				$input_errors[] = gettext("This private does not appear to be valid.");
222
				$input_errors[] = gettext("Key data field should be blank, or a valid x509 private key");
223
			}
224
			if ($_POST['lifetime'] > $max_lifetime) {
225
				$input_errors[] = gettext("Lifetime is longer than the maximum allowed value. Use a shorter lifetime.");
226
			}
227
			break;
228
		case 'edit':
229
		case 'import':
230
			$reqdfields = explode(" ",
231
				"descr cert key");
232
			$reqdfieldsn = array(
233
				gettext("Descriptive name"),
234
				gettext("Certificate data"),
235
				gettext("Key data"));
236
			if ($_POST['cert'] && (!strstr($_POST['cert'], "BEGIN CERTIFICATE") || !strstr($_POST['cert'], "END CERTIFICATE"))) {
237
				$input_errors[] = gettext("This certificate does not appear to be valid.");
238
			}
239

    
240
			if (cert_get_publickey($_POST['cert'], false) != cert_get_publickey($_POST['key'], false, 'prv')) {
241
				$input_errors[] = gettext("The submitted private key does not match the submitted certificate data.");
242
			}
243
			break;
244
		case 'internal':
245
			$reqdfields = explode(" ",
246
				"descr caref keylen ecname keytype type lifetime dn_commonname");
247
			$reqdfieldsn = array(
248
				gettext("Descriptive name"),
249
				gettext("Certificate authority"),
250
				gettext("Key length"),
251
				gettext("Elliptic Curve Name"),
252
				gettext("Key type"),
253
				gettext("Certificate Type"),
254
				gettext("Lifetime"),
255
				gettext("Common Name"));
256
			if ($_POST['lifetime'] > $max_lifetime) {
257
				$input_errors[] = gettext("Lifetime is longer than the maximum allowed value. Use a shorter lifetime.");
258
			}
259
			break;
260
		case 'external':
261
			$reqdfields = explode(" ",
262
				"descr csr_keylen csr_ecname csr_keytype csr_dn_commonname");
263
			$reqdfieldsn = array(
264
				gettext("Descriptive name"),
265
				gettext("Key length"),
266
				gettext("Elliptic Curve Name"),
267
				gettext("Key type"),
268
				gettext("Common Name"));
269
			break;
270
		case 'existing':
271
			$reqdfields = array("certref");
272
			$reqdfieldsn = array(gettext("Existing Certificate Choice"));
273
			break;
274
		default:
275
			break;
276
	}
277

    
278
	$altnames = array();
279
	do_input_validation($_POST, $reqdfields, $reqdfieldsn, $input_errors);
280

    
281
	if (!in_array($pconfig['method'], array('edit', 'import', 'existing'))) {
282
		/* subjectAltNames */
283
		$san_typevar = 'altname_type';
284
		$san_valuevar = 'altname_value';
285
		// This is just the blank alternate name that is added for display purposes. We don't want to validate/save it
286
		if ($_POST["{$san_valuevar}0"] == "") {
287
			unset($_POST["{$san_typevar}0"]);
288
			unset($_POST["{$san_valuevar}0"]);
289
		}
290
		foreach ($_POST as $key => $value) {
291
			$entry = '';
292
			if (!substr_compare($san_typevar, $key, 0, strlen($san_typevar))) {
293
				$entry = substr($key, strlen($san_typevar));
294
				$field = 'type';
295
			} elseif (!substr_compare($san_valuevar, $key, 0, strlen($san_valuevar))) {
296
				$entry = substr($key, strlen($san_valuevar));
297
				$field = 'value';
298
			}
299

    
300
			if (ctype_digit($entry)) {
301
				$entry++;	// Pre-bootstrap code is one-indexed, but the bootstrap code is 0-indexed
302
				$altnames[$entry][$field] = $value;
303
			}
304
		}
305

    
306
		$pconfig['altnames']['item'] = $altnames;
307

    
308
		/* Input validation for subjectAltNames */
309
		foreach ($altnames as $idx => $altname) {
310
			switch ($altname['type']) {
311
				case "DNS":
312
					if (!is_hostname($altname['value'], true) || is_ipaddr($altname['value'])) {
313
						$input_errors[] = gettext("DNS subjectAltName values must be valid hostnames, FQDNs or wildcard domains.");
314
					}
315
					break;
316
				case "IP":
317
					if (!is_ipaddr($altname['value'])) {
318
						$input_errors[] = gettext("IP subjectAltName values must be valid IP Addresses");
319
					}
320
					break;
321
				case "email":
322
					if (empty($altname['value'])) {
323
						$input_errors[] = gettext("An e-mail address must be provided for this type of subjectAltName");
324
					}
325
					if (preg_match("/[\!\#\$\%\^\(\)\~\?\>\<\&\/\\\,\"\']/", $altname['value'])) {
326
						$input_errors[] = gettext("The e-mail provided in a subjectAltName contains invalid characters.");
327
					}
328
					break;
329
				case "URI":
330
					/* Close enough? */
331
					if (!is_URL($altname['value'])) {
332
						$input_errors[] = gettext("URI subjectAltName types must be a valid URI");
333
					}
334
					break;
335
				default:
336
					$input_errors[] = gettext("Unrecognized subjectAltName type.");
337
			}
338
		}
339

    
340
		/* Make sure we do not have invalid characters in the fields for the certificate */
341
		if (preg_match("/[\?\>\<\&\/\\\"\']/", $_POST['descr'])) {
342
			$input_errors[] = gettext("The field 'Descriptive Name' contains invalid characters.");
343
		}
344

    
345
		switch ($pconfig['method']) {
346
			case "internal":
347
				if (isset($_POST["keytype"]) && !in_array($_POST["keytype"], $cert_keytypes)) {
348
					$input_errors[] = gettext("Please select a valid Key Type.");
349
				}
350
				if (isset($_POST["keylen"]) && !in_array($_POST["keylen"], $cert_keylens)) {
351
					$input_errors[] = gettext("Please select a valid Key Length.");
352
				}
353
				if (isset($_POST["ecname"]) && !in_array($_POST["ecname"], array_keys($openssl_ecnames))) {
354
					$input_errors[] = gettext("Please select a valid Elliptic Curve Name.");
355
				}
356
				if (!in_array($_POST["digest_alg"], $openssl_digest_algs)) {
357
					$input_errors[] = gettext("Please select a valid Digest Algorithm.");
358
				}
359
				break;
360
			case "external":
361
				if (isset($_POST["csr_keytype"]) && !in_array($_POST["csr_keytype"], $cert_keytypes)) {
362
					$input_errors[] = gettext("Please select a valid Key Type.");
363
				}
364
				if (isset($_POST["csr_keylen"]) && !in_array($_POST["csr_keylen"], $cert_keylens)) {
365
					$input_errors[] = gettext("Please select a valid Key Length.");
366
				}
367
				if (isset($_POST["csr_ecname"]) && !in_array($_POST["csr_ecname"], array_keys($openssl_ecnames))) {
368
					$input_errors[] = gettext("Please select a valid Elliptic Curve Name.");
369
				}
370
				if (!in_array($_POST["csr_digest_alg"], $openssl_digest_algs)) {
371
					$input_errors[] = gettext("Please select a valid Digest Algorithm.");
372
				}
373
				break;
374
			case "sign":
375
				if (!in_array($_POST["csrsign_digest_alg"], $openssl_digest_algs)) {
376
					$input_errors[] = gettext("Please select a valid Digest Algorithm.");
377
				}
378
				break;
379
			default:
380
				break;
381
		}
382
	}
383

    
384
	/* save modifications */
385
	if (!$input_errors) {
386
		$old_err_level = error_reporting(0); /* otherwise openssl_ functions throw warnings directly to a page breaking menu tabs */
387

    
388
		if (isset($id) && $thiscert) {
389
			$cert = $thiscert;
390
		} else {
391
			$cert = array();
392
			$cert['refid'] = uniqid();
393
		}
394

    
395
		$cert['descr'] = $pconfig['descr'];
396

    
397
		switch($pconfig['method']) {
398
			case 'existing':
399
				/* Add an existing certificate to a user */
400
				$ucert = lookup_cert($pconfig['certref']);
401
				if ($ucert && $a_user) {
402
					$a_user[$userid]['cert'][] = $ucert['refid'];
403
					$savemsg = sprintf(gettext("Added certificate %s to user %s"), $ucert['descr'], $a_user[$userid]['name']);
404
				}
405
				unset($cert);
406
				break;
407
			case 'sign':
408
				/* Sign a CSR */
409
				$csrid = lookup_cert($pconfig['csrtosign']);
410
				$ca = & lookup_ca($pconfig['catosignwith']);
411
				// Read the CSR from $config, or if a new one, from the textarea
412
				if ($pconfig['csrtosign'] === "new") {
413
					$csr = $pconfig['csrpaste'];
414
				} else {
415
					$csr = base64_decode($csrid['csr']);
416
				}
417
				if (count($altnames)) {
418
					foreach ($altnames as $altname) {
419
						$altnames_tmp[] = "{$altname['type']}:" . cert_escape_x509_chars($altname['value']);
420
					}
421
					$altname_str = implode(",", $altnames_tmp);
422
				}
423
				$n509 = csr_sign($csr, $ca, $pconfig['csrsign_lifetime'], $pconfig['type'], $altname_str, $pconfig['csrsign_digest_alg']);
424
				if ($n509) {
425
					// Gather the details required to save the new cert
426
					$newcert = array();
427
					$newcert['refid'] = uniqid();
428
					$newcert['caref'] = $pconfig['catosignwith'];
429
					$newcert['descr'] = $pconfig['descr'];
430
					$newcert['type'] = $pconfig['type'];
431
					$newcert['crt'] = base64_encode($n509);
432
					if ($pconfig['csrtosign'] === "new") {
433
						$newcert['prv'] = base64_encode($pconfig['keypaste']);
434
					} else {
435
						$newcert['prv'] = $csrid['prv'];
436
					}
437
					// Add it to the config file
438
					$config['cert'][] = $newcert;
439
					$savemsg = sprintf(gettext("Signed certificate %s"), $newcert['descr']);
440
				}
441
				unset($cert);
442
				break;
443
			case 'edit':
444
				cert_import($cert, $pconfig['cert'], $pconfig['key']);
445
				$savemsg = sprintf(gettext("Edited certificate %s"), $cert['descr']);
446
				break;
447
			case 'import':
448
				/* Import an external certificate+key */
449
				cert_import($cert, $pconfig['cert'], $pconfig['key']);
450
				$savemsg = sprintf(gettext("Imported certificate %s"), $cert['descr']);
451
				break;
452
			case 'internal':
453
				/* Create an internal certificate */
454
				$dn = array('commonName' => cert_escape_x509_chars($pconfig['dn_commonname']));
455
				if (!empty($pconfig['dn_country'])) {
456
					$dn['countryName'] = $pconfig['dn_country'];
457
				}
458
				if (!empty($pconfig['dn_state'])) {
459
					$dn['stateOrProvinceName'] = cert_escape_x509_chars($pconfig['dn_state']);
460
				}
461
				if (!empty($pconfig['dn_city'])) {
462
					$dn['localityName'] = cert_escape_x509_chars($pconfig['dn_city']);
463
				}
464
				if (!empty($pconfig['dn_organization'])) {
465
					$dn['organizationName'] = cert_escape_x509_chars($pconfig['dn_organization']);
466
				}
467
				if (!empty($pconfig['dn_organizationalunit'])) {
468
					$dn['organizationalUnitName'] = cert_escape_x509_chars($pconfig['dn_organizationalunit']);
469
				}
470
				$altnames_tmp = array();
471
				$cn_altname = cert_add_altname_type($pconfig['dn_commonname']);
472
				if (!empty($cn_altname)) {
473
					$altnames_tmp[] = $cn_altname;
474
				}
475
				if (count($altnames)) {
476
					foreach ($altnames as $altname) {
477
						// The CN is added as a SAN automatically, do not add it again.
478
						if ($altname['value'] != $pconfig['dn_commonname']) {
479
							$altnames_tmp[] = "{$altname['type']}:" . cert_escape_x509_chars($altname['value']);
480
						}
481
					}
482
				}
483
				if (!empty($altnames_tmp)) {
484
					$dn['subjectAltName'] = implode(",", $altnames_tmp);
485
				}
486
				if (!cert_create($cert, $pconfig['caref'], $pconfig['keylen'], $pconfig['lifetime'], $dn, $pconfig['type'], $pconfig['digest_alg'], $pconfig['keytype'], $pconfig['ecname'])) {
487
					$input_errors = array();
488
					while ($ssl_err = openssl_error_string()) {
489
						if (strpos($ssl_err, 'NCONF_get_string:no value') === false) {
490
							$input_errors[] = sprintf(gettext("OpenSSL Library Error: %s"), $ssl_err);
491
						}
492
					}
493
				}
494
				$savemsg = sprintf(gettext("Created internal certificate %s"), $cert['descr']);
495
				break;
496
			case 'external':
497
				/* Create a certificate signing request */
498
				$dn = array('commonName' => cert_escape_x509_chars($pconfig['csr_dn_commonname']));
499
				if (!empty($pconfig['csr_dn_country'])) {
500
					$dn['countryName'] = $pconfig['csr_dn_country'];
501
				}
502
				if (!empty($pconfig['csr_dn_state'])) {
503
					$dn['stateOrProvinceName'] = cert_escape_x509_chars($pconfig['csr_dn_state']);
504
				}
505
				if (!empty($pconfig['csr_dn_city'])) {
506
					$dn['localityName'] = cert_escape_x509_chars($pconfig['csr_dn_city']);
507
				}
508
				if (!empty($pconfig['csr_dn_organization'])) {
509
					$dn['organizationName'] = cert_escape_x509_chars($pconfig['csr_dn_organization']);
510
				}
511
				if (!empty($pconfig['csr_dn_organizationalunit'])) {
512
					$dn['organizationalUnitName'] = cert_escape_x509_chars($pconfig['csr_dn_organizationalunit']);
513
				}
514
				$altnames_tmp = array();
515
				$cn_altname = cert_add_altname_type($pconfig['csr_dn_commonname']);
516
				if (!empty($cn_altname)) {
517
					$altnames_tmp[] = $cn_altname;
518
				}
519
				if (count($altnames)) {
520
					foreach ($altnames as $altname) {
521
						// The CN is added as a SAN automatically, do not add it again.
522
						if ($altname['value'] != $pconfig['csr_dn_commonname']) {
523
							$altnames_tmp[] = "{$altname['type']}:" . cert_escape_x509_chars($altname['value']);
524
						}
525
					}
526
				}
527
				if (!empty($altnames_tmp)) {
528
					$dn['subjectAltName'] = implode(",", $altnames_tmp);
529
				}
530
				if (!csr_generate($cert, $pconfig['csr_keylen'], $dn, $pconfig['type'], $pconfig['csr_digest_alg'], $pconfig['csr_keytype'], $pconfig['csr_ecname'])) {
531
					$input_errors = array();
532
					while ($ssl_err = openssl_error_string()) {
533
						if (strpos($ssl_err, 'NCONF_get_string:no value') === false) {
534
							$input_errors[] = sprintf(gettext("OpenSSL Library Error: %s"), $ssl_err);
535
						}
536
					}
537
				}
538
				$savemsg = sprintf(gettext("Created certificate signing request %s"), $cert['descr']);
539
				break;
540
			default:
541
				break;
542
		}
543
		error_reporting($old_err_level);
544

    
545
		if (isset($id) && $thiscert) {
546
			$thiscert = $cert;
547
		} elseif ($cert) {
548
			$a_cert[] = $cert;
549
		}
550

    
551
		if (isset($a_user) && isset($userid)) {
552
			$a_user[$userid]['cert'][] = $cert['refid'];
553
		}
554

    
555
		if (!$input_errors) {
556
			write_config($savemsg);
557
		}
558

    
559
		if ((isset($userid) && is_numeric($userid)) && !$input_errors) {
560
			post_redirect("system_usermanager.php", array('act' => 'edit', 'userid' => $userid));
561
			exit;
562
		}
563
	}
564
} elseif ($_POST['save'] == gettext("Update")) {
565
	/* Updating a certificate signing request */
566
	unset($input_errors);
567
	$pconfig = $_POST;
568

    
569
	/* input validation */
570
	$reqdfields = explode(" ", "descr cert");
571
	$reqdfieldsn = array(
572
		gettext("Descriptive name"),
573
		gettext("Final Certificate data"));
574

    
575
	do_input_validation($_POST, $reqdfields, $reqdfieldsn, $input_errors);
576

    
577
	if (preg_match("/[\?\>\<\&\/\\\"\']/", $_POST['descr'])) {
578
		$input_errors[] = gettext("The field 'Descriptive Name' contains invalid characters.");
579
	}
580

    
581
	$mod_csr = cert_get_publickey($pconfig['csr'], false, 'csr');
582
	$mod_cert = cert_get_publickey($pconfig['cert'], false);
583

    
584
	if (strcmp($mod_csr, $mod_cert)) {
585
		// simply: if the moduli don't match, then the private key and public key won't match
586
		$input_errors[] = gettext("The certificate public key does not match the signing request public key.");
587
		$subject_mismatch = true;
588
	}
589

    
590
	/* save modifications */
591
	if (!$input_errors) {
592
		$cert = $thiscert;
593
		$cert['descr'] = $pconfig['descr'];
594
		csr_complete($cert, $pconfig['cert']);
595
		$thiscert = $cert;
596
		$savemsg = sprintf(gettext("Updated certificate signing request %s"), $pconfig['descr']);
597
		write_config($savemsg);
598
		pfSenseHeader("system_certmanager.php");
599
	}
600
}
601

    
602
$pgtitle = array(gettext("System"), gettext("Certificate Manager"), gettext("Certificates"));
603
$pglinks = array("", "system_camanager.php", "system_certmanager.php");
604

    
605
if (($act == "new" || ($_POST['save'] == gettext("Save") && $input_errors)) ||
606
    ($act == "csr" || ($_POST['save'] == gettext("Update") && $input_errors))) {
607
	$pgtitle[] = gettext('Edit');
608
	$pglinks[] = "@self";
609
}
610
include("head.inc");
611

    
612
if ($input_errors) {
613
	print_input_errors($input_errors);
614
}
615

    
616
if ($savemsg) {
617
	print_info_box($savemsg, $class);
618
}
619

    
620
$tab_array = array();
621
$tab_array[] = array(gettext("CAs"), false, "system_camanager.php");
622
$tab_array[] = array(gettext("Certificates"), true, "system_certmanager.php");
623
$tab_array[] = array(gettext("Certificate Revocation"), false, "system_crlmanager.php");
624
display_top_tabs($tab_array);
625

    
626
if (in_array($act, array('new', 'edit')) || (($_POST['save'] == gettext("Save")) && $input_errors)) {
627
	$form = new Form();
628
	$form->setAction('system_certmanager.php');
629

    
630
	if (isset($userid) && $a_user) {
631
		$form->addGlobal(new Form_Input(
632
			'userid',
633
			null,
634
			'hidden',
635
			$userid
636
		));
637
	}
638

    
639
	if (isset($id) && $thiscert) {
640
		$form->addGlobal(new Form_Input(
641
			'id',
642
			null,
643
			'hidden',
644
			$id
645
		));
646
	}
647

    
648
	switch ($act) {
649
		case 'edit':
650
			$maintitle = gettext('Edit an Existing Certificate');
651
			break;
652
		case 'new':
653
		default:
654
			$maintitle = gettext('Add/Sign a New Certificate');
655
			break;
656
	}
657

    
658
	$section = new Form_Section($maintitle);
659

    
660
	if (!isset($id) || ($act == 'edit')) {
661
		$section->addInput(new Form_Select(
662
			'method',
663
			'*Method',
664
			$pconfig['method'],
665
			$cert_methods
666
		))->toggles();
667
	}
668

    
669
	$section->addInput(new Form_Input(
670
		'descr',
671
		'*Descriptive name',
672
		'text',
673
		($a_user && empty($pconfig['descr'])) ? $a_user[$userid]['name'] : $pconfig['descr']
674
	))->addClass('toggle-internal toggle-import toggle-edit toggle-external toggle-sign toggle-existing collapse');
675

    
676
	if (!empty($pconfig['cert'])) {
677
		$section->addInput(new Form_StaticText(
678
			"Subject",
679
			htmlspecialchars(cert_get_subject($pconfig['cert'], false))
680
		))->addClass('toggle-edit collapse');
681
	}
682

    
683
	$form->add($section);
684

    
685
	// Return an array containing the IDs od all CAs
686
	function list_cas() {
687
		global $a_ca;
688
		$allCas = array();
689

    
690
		foreach ($a_ca as $ca) {
691
			if ($ca['prv']) {
692
				$allCas[$ca['refid']] = $ca['descr'];
693
			}
694
		}
695

    
696
		return $allCas;
697
	}
698

    
699
	// Return an array containing the IDs od all CSRs
700
	function list_csrs() {
701
		global $config;
702
		$allCsrs = array();
703

    
704
		foreach ($config['cert'] as $cert) {
705
			if ($cert['csr']) {
706
				$allCsrs[$cert['refid']] = $cert['descr'];
707
			}
708
		}
709

    
710
		return ['new' => gettext('New CSR (Paste below)')] + $allCsrs;
711
	}
712

    
713
	$section = new Form_Section('Sign CSR');
714
	$section->addClass('toggle-sign collapse');
715

    
716
	$section->AddInput(new Form_Select(
717
		'catosignwith',
718
		'*CA to sign with',
719
		$pconfig['catosignwith'],
720
		list_cas()
721
	));
722

    
723
	$section->AddInput(new Form_Select(
724
		'csrtosign',
725
		'*CSR to sign',
726
		isset($pconfig['csrtosign']) ? $pconfig['csrtosign'] : 'new',
727
		list_csrs()
728
	));
729

    
730
	$section->addInput(new Form_Textarea(
731
		'csrpaste',
732
		'CSR data',
733
		$pconfig['csrpaste']
734
	))->setHelp('Paste a Certificate Signing Request in X.509 PEM format here.');
735

    
736
	$section->addInput(new Form_Textarea(
737
		'keypaste',
738
		'Key data',
739
		$pconfig['keypaste']
740
	))->setHelp('Optionally paste a private key here. The key will be associated with the newly signed certificate in pfSense');
741

    
742
	$section->addInput(new Form_Input(
743
		'csrsign_lifetime',
744
		'*Certificate Lifetime (days)',
745
		'number',
746
		$pconfig['csrsign_lifetime'] ? $pconfig['csrsign_lifetime']:$default_lifetime,
747
		['max' => $max_lifetime]
748
	))->setHelp('The length of time the signed certificate will be valid, in days. %1$s' .
749
		'Server certificates should not have a lifetime over 825 days or some platforms ' .
750
		'may consider the certificate invalid.', '<br/>');
751
	$section->addInput(new Form_Select(
752
		'csrsign_digest_alg',
753
		'*Digest Algorithm',
754
		$pconfig['csrsign_digest_alg'],
755
		array_combine($openssl_digest_algs, $openssl_digest_algs)
756
	))->setHelp('The digest method used when the certificate is signed. %1$s' .
757
		'The best practice is to use an algorithm stronger than SHA1. '.
758
		'Some platforms may consider weaker digest algorithms invalid', '<br/>');
759

    
760
	$form->add($section);
761

    
762
	if ($act == 'edit') {
763
		$editimport = gettext("Edit Certificate");
764
	} else {
765
		$editimport = gettext("Import Certificate");
766
	}
767

    
768
	$section = new Form_Section($editimport);
769
	$section->addClass('toggle-import toggle-edit collapse');
770

    
771
	$section->addInput(new Form_Textarea(
772
		'cert',
773
		'*Certificate data',
774
		$pconfig['cert']
775
	))->setHelp('Paste a certificate in X.509 PEM format here.');
776

    
777
	$section->addInput(new Form_Textarea(
778
		'key',
779
		'*Private key data',
780
		$pconfig['key']
781
	))->setHelp('Paste a private key in X.509 PEM format here.');
782

    
783
	if ($act == 'edit') {
784
		$section->addInput(new Form_Input(
785
			'exportpass',
786
			'Export Password',
787
			'password',
788
			null,
789
			['placeholder' => gettext('Export Password'), 'autocomplete' => 'new-password']
790
		))->setHelp('Enter the password to use when using the export buttons below (not stored)')->addClass('toggle-edit collapse');
791
	}
792

    
793
	$form->add($section);
794
	$section = new Form_Section('Internal Certificate');
795
	$section->addClass('toggle-internal collapse');
796

    
797
	if (!$internal_ca_count) {
798
		$section->addInput(new Form_StaticText(
799
			'*Certificate authority',
800
			gettext('No internal Certificate Authorities have been defined. ') .
801
			gettext('An internal CA must be defined in order to create an internal certificate. ') .
802
			sprintf(gettext('%1$sCreate%2$s an internal CA.'), '<a href="system_camanager.php?act=new&amp;method=internal"> ', '</a>')
803
		));
804
	} else {
805
		$allCas = array();
806
		foreach ($a_ca as $ca) {
807
			if (!$ca['prv']) {
808
				continue;
809
			}
810

    
811
			$allCas[ $ca['refid'] ] = $ca['descr'];
812
		}
813

    
814
		$section->addInput(new Form_Select(
815
			'caref',
816
			'*Certificate authority',
817
			$pconfig['caref'],
818
			$allCas
819
		));
820
	}
821

    
822
	$section->addInput(new Form_Select(
823
		'keytype',
824
		'*Key type',
825
		$pconfig['keytype'],
826
		array_combine($cert_keytypes, $cert_keytypes)
827
	));
828

    
829
	$group = new Form_Group($i == 0 ? '*Key length':'');
830
	$group->addClass('rsakeys');
831
	$group->add(new Form_Select(
832
		'keylen',
833
		null,
834
		$pconfig['keylen'],
835
		array_combine($cert_keylens, $cert_keylens)
836
	))->setHelp('The length to use when generating a new RSA key, in bits. %1$s' .
837
		'The Key Length should not be lower than 2048 or some platforms ' .
838
		'may consider the certificate invalid.', '<br/>');
839
	$section->add($group);
840

    
841
	$group = new Form_Group($i == 0 ? '*Elliptic Curve Name':'');
842
	$group->addClass('ecnames');
843
	$group->add(new Form_Select(
844
		'ecname',
845
		null,
846
		$pconfig['ecname'],
847
		$openssl_ecnames
848
	))->setHelp('Curves may not be compatible with all uses. Known compatible curve uses are denoted in brackets.');
849
	$section->add($group);
850

    
851
	$section->addInput(new Form_Select(
852
		'digest_alg',
853
		'*Digest Algorithm',
854
		$pconfig['digest_alg'],
855
		array_combine($openssl_digest_algs, $openssl_digest_algs)
856
	))->setHelp('The digest method used when the certificate is signed. %1$s' .
857
		'The best practice is to use an algorithm stronger than SHA1. '.
858
		'Some platforms may consider weaker digest algorithms invalid', '<br/>');
859

    
860
	$section->addInput(new Form_Input(
861
		'lifetime',
862
		'*Lifetime (days)',
863
		'number',
864
		$pconfig['lifetime'],
865
		['max' => $max_lifetime]
866
	))->setHelp('The length of time the signed certificate will be valid, in days. %1$s' .
867
		'Server certificates should not have a lifetime over 825 days or some platforms ' .
868
		'may consider the certificate invalid.', '<br/>');
869

    
870
	$section->addInput(new Form_Input(
871
		'dn_commonname',
872
		'*Common Name',
873
		'text',
874
		$pconfig['dn_commonname'],
875
		['placeholder' => 'e.g. www.example.com']
876
	));
877

    
878
	$section->addInput(new Form_StaticText(
879
		null,
880
		gettext('The following certificate subject components are optional and may be left blank.')
881
	));
882

    
883
	$section->addInput(new Form_Select(
884
		'dn_country',
885
		'Country Code',
886
		$pconfig['dn_country'],
887
		get_cert_country_codes()
888
	));
889

    
890
	$section->addInput(new Form_Input(
891
		'dn_state',
892
		'State or Province',
893
		'text',
894
		$pconfig['dn_state'],
895
		['placeholder' => 'e.g. Texas']
896
	));
897

    
898
	$section->addInput(new Form_Input(
899
		'dn_city',
900
		'City',
901
		'text',
902
		$pconfig['dn_city'],
903
		['placeholder' => 'e.g. Austin']
904
	));
905

    
906
	$section->addInput(new Form_Input(
907
		'dn_organization',
908
		'Organization',
909
		'text',
910
		$pconfig['dn_organization'],
911
		['placeholder' => 'e.g. My Company Inc']
912
	));
913

    
914
	$section->addInput(new Form_Input(
915
		'dn_organizationalunit',
916
		'Organizational Unit',
917
		'text',
918
		$pconfig['dn_organizationalunit'],
919
		['placeholder' => 'e.g. My Department Name (optional)']
920
	));
921

    
922
	$form->add($section);
923
	$section = new Form_Section('External Signing Request');
924
	$section->addClass('toggle-external collapse');
925

    
926
	$section->addInput(new Form_Select(
927
		'csr_keytype',
928
		'*Key type',
929
		$pconfig['csr_keytype'],
930
		array_combine($cert_keytypes, $cert_keytypes)
931
	));
932

    
933
	$group = new Form_Group($i == 0 ? '*Key length':'');
934
	$group->addClass('csr_rsakeys');
935
	$group->add(new Form_Select(
936
		'csr_keylen',
937
		null,
938
		$pconfig['csr_keylen'],
939
		array_combine($cert_keylens, $cert_keylens)
940
	))->setHelp('The length to use when generating a new RSA key, in bits. %1$s' .
941
		'The Key Length should not be lower than 2048 or some platforms ' .
942
		'may consider the certificate invalid.', '<br/>');
943
	$section->add($group);
944

    
945
	$group = new Form_Group($i == 0 ? '*Elliptic Curve Name':'');
946
	$group->addClass('csr_ecnames');
947
	$group->add(new Form_Select(
948
		'csr_ecname',
949
		null,
950
		$pconfig['csr_ecname'],
951
		$openssl_ecnames
952
	));
953
	$section->add($group);
954

    
955
	$section->addInput(new Form_Select(
956
		'csr_digest_alg',
957
		'*Digest Algorithm',
958
		$pconfig['csr_digest_alg'],
959
		array_combine($openssl_digest_algs, $openssl_digest_algs)
960
	))->setHelp('The digest method used when the certificate is signed. %1$s' .
961
		'The best practice is to use an algorithm stronger than SHA1. '.
962
		'Some platforms may consider weaker digest algorithms invalid', '<br/>');
963

    
964
	$section->addInput(new Form_Input(
965
		'csr_dn_commonname',
966
		'*Common Name',
967
		'text',
968
		$pconfig['csr_dn_commonname'],
969
		['placeholder' => 'e.g. internal-ca']
970
	));
971

    
972
	$section->addInput(new Form_StaticText(
973
		null,
974
		gettext('The following certificate subject components are optional and may be left blank.')
975
	));
976

    
977
	$section->addInput(new Form_Select(
978
		'csr_dn_country',
979
		'Country Code',
980
		$pconfig['csr_dn_country'],
981
		get_cert_country_codes()
982
	));
983

    
984
	$section->addInput(new Form_Input(
985
		'csr_dn_state',
986
		'State or Province',
987
		'text',
988
		$pconfig['csr_dn_state'],
989
		['placeholder' => 'e.g. Texas']
990
	));
991

    
992
	$section->addInput(new Form_Input(
993
		'csr_dn_city',
994
		'City',
995
		'text',
996
		$pconfig['csr_dn_city'],
997
		['placeholder' => 'e.g. Austin']
998
	));
999

    
1000
	$section->addInput(new Form_Input(
1001
		'csr_dn_organization',
1002
		'Organization',
1003
		'text',
1004
		$pconfig['csr_dn_organization'],
1005
		['placeholder' => 'e.g. My Company Inc']
1006
	));
1007

    
1008
	$section->addInput(new Form_Input(
1009
		'csr_dn_organizationalunit',
1010
		'Organizational Unit',
1011
		'text',
1012
		$pconfig['csr_dn_organizationalunit'],
1013
		['placeholder' => 'e.g. My Department Name (optional)']
1014
	));
1015

    
1016
	$form->add($section);
1017
	$section = new Form_Section('Choose an Existing Certificate');
1018
	$section->addClass('toggle-existing collapse');
1019

    
1020
	$existCerts = array();
1021

    
1022
	foreach ($config['cert'] as $cert) {
1023
		if (!is_array($cert) || empty($cert)) {
1024
			continue;
1025
		}
1026
		if (is_array($config['system']['user'][$userid]['cert'])) { // Could be MIA!
1027
			if (isset($userid) && in_array($cert['refid'], $config['system']['user'][$userid]['cert'])) {
1028
				continue;
1029
			}
1030
		}
1031

    
1032
		$ca = lookup_ca($cert['caref']);
1033
		if ($ca) {
1034
			$cert['descr'] .= " (CA: {$ca['descr']})";
1035
		}
1036

    
1037
		if (cert_in_use($cert['refid'])) {
1038
			$cert['descr'] .= " (In Use)";
1039
		}
1040
		if (is_cert_revoked($cert)) {
1041
			$cert['descr'] .= " (Revoked)";
1042
		}
1043

    
1044
		$existCerts[ $cert['refid'] ] = $cert['descr'];
1045
	}
1046

    
1047
	$section->addInput(new Form_Select(
1048
		'certref',
1049
		'*Existing Certificates',
1050
		$pconfig['certref'],
1051
		$existCerts
1052
	));
1053

    
1054
	$form->add($section);
1055

    
1056
	$section = new Form_Section('Certificate Attributes');
1057
	$section->addClass('toggle-external toggle-internal toggle-sign collapse');
1058

    
1059
	$section->addInput(new Form_StaticText(
1060
		gettext('Attribute Notes'),
1061
		'<span class="help-block">'.
1062
		gettext('The following attributes are added to certificates and ' .
1063
		'requests when they are created or signed. These attributes behave ' .
1064
		'differently depending on the selected mode.') .
1065
		'<br/><br/>' .
1066
		'<span class="toggle-internal collapse">' . gettext('For Internal Certificates, these attributes are added directly to the certificate as shown.') . '</span>' .
1067
		'<span class="toggle-external collapse">' .
1068
		gettext('For Certificate Signing Requests, These attributes are added to the request but they may be ignored or changed by the CA that signs the request. ') .
1069
		'<br/><br/>' .
1070
		gettext('If this CSR will be signed using the Certificate Manager on this firewall, set the attributes when signing instead as they cannot be carried over.') . '</span>' .
1071
		'<span class="toggle-sign collapse">' . gettext('When Signing a Certificate Request, existing attributes in the request cannot be copied. The attributes below will be applied to the resulting certificate.') . '</span>' .
1072
		'</span>'
1073
	));
1074

    
1075
	$section->addInput(new Form_Select(
1076
		'type',
1077
		'*Certificate Type',
1078
		$pconfig['type'],
1079
		$cert_types
1080
	))->setHelp('Add type-specific usage attributes to the signed certificate.' .
1081
		' Used for placing usage restrictions on, or granting abilities to, ' .
1082
		'the signed certificate.');
1083

    
1084
	if (empty($pconfig['altnames']['item'])) {
1085
		$pconfig['altnames']['item'] = array(
1086
			array('type' => null, 'value' => null)
1087
		);
1088
	}
1089

    
1090
	$counter = 0;
1091
	$numrows = count($pconfig['altnames']['item']) - 1;
1092

    
1093
	foreach ($pconfig['altnames']['item'] as $item) {
1094

    
1095
		$group = new Form_Group($counter == 0 ? 'Alternative Names':'');
1096

    
1097
		$group->add(new Form_Select(
1098
			'altname_type' . $counter,
1099
			'Type',
1100
			$item['type'],
1101
			$cert_altname_types
1102
		))->setHelp(($counter == $numrows) ? 'Type':null);
1103

    
1104
		$group->add(new Form_Input(
1105
			'altname_value' . $counter,
1106
			null,
1107
			'text',
1108
			$item['value']
1109
		))->setHelp(($counter == $numrows) ? 'Value':null);
1110

    
1111
		$group->add(new Form_Button(
1112
			'deleterow' . $counter,
1113
			'Delete',
1114
			null,
1115
			'fa-trash'
1116
		))->addClass('btn-warning');
1117

    
1118
		$group->addClass('repeatable');
1119

    
1120
		$group->setHelp('Enter additional identifiers for the certificate ' .
1121
			'in this list. The Common Name field is automatically ' .
1122
			'added to the certificate as an Alternative Name. ' .
1123
			'The signing CA may ignore or change these values.');
1124

    
1125
		$section->add($group);
1126

    
1127
		$counter++;
1128
	}
1129

    
1130
	$section->addInput(new Form_Button(
1131
		'addrow',
1132
		'Add',
1133
		null,
1134
		'fa-plus'
1135
	))->addClass('btn-success');
1136

    
1137
	$form->add($section);
1138

    
1139
	if ($act == 'edit') {
1140
		$form->addGlobal(new Form_Button(
1141
			'exportpkey',
1142
			'Export Private Key',
1143
			null,
1144
			'fa-key'
1145
		))->addClass('btn-primary');
1146
		$form->addGlobal(new Form_Button(
1147
			'exportp12',
1148
			'Export PKCS#12',
1149
			null,
1150
			'fa-archive'
1151
		))->addClass('btn-primary');
1152
	}
1153

    
1154
	print $form;
1155

    
1156
} elseif ($act == "csr" || (($_POST['save'] == gettext("Update")) && $input_errors)) {
1157
	$form = new Form(false);
1158
	$form->setAction('system_certmanager.php?act=csr');
1159

    
1160
	$section = new Form_Section("Complete Signing Request for " . $pconfig['descr']);
1161

    
1162
	$section->addInput(new Form_Input(
1163
		'descr',
1164
		'*Descriptive name',
1165
		'text',
1166
		$pconfig['descr']
1167
	));
1168

    
1169
	$section->addInput(new Form_Textarea(
1170
		'csr',
1171
		'Signing request data',
1172
		$pconfig['csr']
1173
	))->setReadonly()
1174
	  ->setWidth(7)
1175
	  ->setHelp('Copy the certificate signing data from here and forward it to a certificate authority for signing.');
1176

    
1177
	$section->addInput(new Form_Textarea(
1178
		'cert',
1179
		'*Final certificate data',
1180
		$pconfig['cert']
1181
	))->setWidth(7)
1182
	  ->setHelp('Paste the certificate received from the certificate authority here.');
1183

    
1184
	if (isset($id) && $thiscert) {
1185
		$form->addGlobal(new Form_Input(
1186
			'id',
1187
			null,
1188
			'hidden',
1189
			$id
1190
		));
1191

    
1192
		$form->addGlobal(new Form_Input(
1193
			'act',
1194
			null,
1195
			'hidden',
1196
			'csr'
1197
		));
1198
	}
1199

    
1200
	$form->add($section);
1201

    
1202
	$form->addGlobal(new Form_Button(
1203
		'save',
1204
		'Update',
1205
		null,
1206
		'fa-save'
1207
	))->addClass('btn-primary');
1208

    
1209
	print($form);
1210
} else {
1211
?>
1212
<div class="panel panel-default" id="search-panel">
1213
	<div class="panel-heading">
1214
		<h2 class="panel-title">
1215
			<?=gettext('Search')?>
1216
			<span class="widget-heading-icon pull-right">
1217
				<a data-toggle="collapse" href="#search-panel_panel-body">
1218
					<i class="fa fa-plus-circle"></i>
1219
				</a>
1220
			</span>
1221
		</h2>
1222
	</div>
1223
	<div id="search-panel_panel-body" class="panel-body collapse in">
1224
		<div class="form-group">
1225
			<label class="col-sm-2 control-label">
1226
				<?=gettext("Search term")?>
1227
			</label>
1228
			<div class="col-sm-5"><input class="form-control" name="searchstr" id="searchstr" type="text"/></div>
1229
			<div class="col-sm-2">
1230
				<select id="where" class="form-control">
1231
					<option value="0"><?=gettext("Name")?></option>
1232
					<option value="1"><?=gettext("Distinguished Name")?></option>
1233
					<option value="2" selected><?=gettext("Both")?></option>
1234
				</select>
1235
			</div>
1236
			<div class="col-sm-3">
1237
				<a id="btnsearch" title="<?=gettext("Search")?>" class="btn btn-primary btn-sm"><i class="fa fa-search icon-embed-btn"></i><?=gettext("Search")?></a>
1238
				<a id="btnclear" title="<?=gettext("Clear")?>" class="btn btn-info btn-sm"><i class="fa fa-undo icon-embed-btn"></i><?=gettext("Clear")?></a>
1239
			</div>
1240
			<div class="col-sm-10 col-sm-offset-2">
1241
				<span class="help-block"><?=gettext('Enter a search string or *nix regular expression to search certificate names and distinguished names.')?></span>
1242
			</div>
1243
		</div>
1244
	</div>
1245
</div>
1246
<div class="panel panel-default">
1247
	<div class="panel-heading"><h2 class="panel-title"><?=gettext('Certificates')?></h2></div>
1248
	<div class="panel-body">
1249
		<div class="table-responsive">
1250
		<table class="table table-striped table-hover sortable-theme-bootstrap" data-sortable>
1251
			<thead>
1252
				<tr>
1253
					<th><?=gettext("Name")?></th>
1254
					<th><?=gettext("Issuer")?></th>
1255
					<th><?=gettext("Distinguished Name")?></th>
1256
					<th><?=gettext("In Use")?></th>
1257

    
1258
					<th class="col-sm-2"><?=gettext("Actions")?></th>
1259
				</tr>
1260
			</thead>
1261
			<tbody>
1262
<?php
1263

    
1264
$pluginparams = array();
1265
$pluginparams['type'] = 'certificates';
1266
$pluginparams['event'] = 'used_certificates';
1267
$certificates_used_by_packages = pkg_call_plugins('plugin_certificates', $pluginparams);
1268
foreach ($a_cert as $cert):
1269
	if (!is_array($cert) || empty($cert)) {
1270
		continue;
1271
	}
1272
	$name = htmlspecialchars($cert['descr']);
1273
	if ($cert['crt']) {
1274
		$subj = cert_get_subject($cert['crt']);
1275
		$issuer = cert_get_issuer($cert['crt']);
1276
		$purpose = cert_get_purpose($cert['crt']);
1277

    
1278
		if ($subj == $issuer) {
1279
			$caname = '<i>'. gettext("self-signed") .'</i>';
1280
		} else {
1281
			$caname = '<i>'. gettext("external").'</i>';
1282
		}
1283

    
1284
		$subj = htmlspecialchars(cert_escape_x509_chars($subj, true));
1285
	} else {
1286
		$subj = "";
1287
		$issuer = "";
1288
		$purpose = "";
1289
		$startdate = "";
1290
		$enddate = "";
1291
		$caname = "<em>" . gettext("private key only") . "</em>";
1292
	}
1293

    
1294
	if ($cert['csr']) {
1295
		$subj = htmlspecialchars(cert_escape_x509_chars(csr_get_subject($cert['csr']), true));
1296
		$caname = "<em>" . gettext("external - signature pending") . "</em>";
1297
	}
1298

    
1299
	$ca = lookup_ca($cert['caref']);
1300
	if ($ca) {
1301
		$caname = $ca['descr'];
1302
	}
1303
?>
1304
				<tr>
1305
					<td>
1306
						<?=$name?><br />
1307
						<?php if ($cert['type']): ?>
1308
							<i><?=$cert_types[$cert['type']]?></i><br />
1309
						<?php endif?>
1310
						<?php if (is_array($purpose)): ?>
1311
							CA: <b><?=$purpose['ca']?></b><br/>
1312
							<?=gettext("Server")?>: <b><?=$purpose['server']?></b><br/>
1313
						<?php endif?>
1314
					</td>
1315
					<td><?=$caname?></td>
1316
					<td>
1317
						<?=$subj?>
1318
						<?= cert_print_infoblock($cert); ?>
1319
						<?php cert_print_dates($cert);?>
1320
					</td>
1321
					<td>
1322
						<?php if (is_cert_revoked($cert)): ?>
1323
							<i><?=gettext("Revoked")?></i>
1324
						<?php endif?>
1325
						<?php if (is_webgui_cert($cert['refid'])): ?>
1326
							<?=gettext("webConfigurator")?>
1327
						<?php endif?>
1328
						<?php if (is_user_cert($cert['refid'])): ?>
1329
							<?=gettext("User Cert")?>
1330
						<?php endif?>
1331
						<?php if (is_openvpn_server_cert($cert['refid'])): ?>
1332
							<?=gettext("OpenVPN Server")?>
1333
						<?php endif?>
1334
						<?php if (is_openvpn_client_cert($cert['refid'])): ?>
1335
							<?=gettext("OpenVPN Client")?>
1336
						<?php endif?>
1337
						<?php if (is_ipsec_cert($cert['refid'])): ?>
1338
							<?=gettext("IPsec Tunnel")?>
1339
						<?php endif?>
1340
						<?php if (is_captiveportal_cert($cert['refid'])): ?>
1341
							<?=gettext("Captive Portal")?>
1342
						<?php endif?>
1343
						<?php echo cert_usedby_description($cert['refid'], $certificates_used_by_packages); ?>
1344
					</td>
1345
					<td>
1346
						<?php if (!$cert['csr']): ?>
1347
							<a href="system_certmanager.php?act=edit&amp;id=<?=$cert['refid']?>" class="fa fa-pencil" title="<?=gettext("Edit Certificate")?>"></a>
1348
							<a href="system_certmanager.php?act=exp&amp;id=<?=$cert['refid']?>" class="fa fa-certificate" title="<?=gettext("Export Certificate")?>"></a>
1349
							<?php if ($cert['prv']): ?>
1350
								<a href="system_certmanager.php?act=key&amp;id=<?=$cert['refid']?>" class="fa fa-key" title="<?=gettext("Export Key")?>"></a>
1351
							<?php endif?>
1352
							<?php if (is_cert_locally_renewable($cert)): ?>
1353
								<a href="system_certmanager_renew.php?type=cert&amp;refid=<?=$cert['refid']?>" class="fa fa-repeat" title="<?=gettext("Reissue/Renew")?>"></a>
1354
							<?php endif ?>
1355
							<a href="system_certmanager.php?act=p12&amp;id=<?=$cert['refid']?>" class="fa fa-archive" title="<?=gettext("Export P12")?>"></a>
1356
						<?php else: ?>
1357
							<a href="system_certmanager.php?act=csr&amp;id=<?=$cert['refid']?>" class="fa fa-pencil" title="<?=gettext("Update CSR")?>"></a>
1358
							<a href="system_certmanager.php?act=req&amp;id=<?=$cert['refid']?>" class="fa fa-sign-in" title="<?=gettext("Export Request")?>"></a>
1359
							<a href="system_certmanager.php?act=key&amp;id=<?=$cert['refid']?>" class="fa fa-key" title="<?=gettext("Export Key")?>"></a>
1360
						<?php endif?>
1361
						<?php if (!cert_in_use($cert['refid'])): ?>
1362
							<a href="system_certmanager.php?act=del&amp;id=<?=$cert['refid']?>" class="fa fa-trash" title="<?=gettext("Delete Certificate")?>" usepost></a>
1363
						<?php endif?>
1364
					</td>
1365
				</tr>
1366
<?php
1367
	endforeach; ?>
1368
			</tbody>
1369
		</table>
1370
		</div>
1371
	</div>
1372
</div>
1373

    
1374
<nav class="action-buttons">
1375
	<a href="?act=new" class="btn btn-success btn-sm">
1376
		<i class="fa fa-plus icon-embed-btn"></i>
1377
		<?=gettext("Add/Sign")?>
1378
	</a>
1379
</nav>
1380
<script type="text/javascript">
1381
//<![CDATA[
1382

    
1383
events.push(function() {
1384

    
1385
	// Make these controls plain buttons
1386
	$("#btnsearch").prop('type', 'button');
1387
	$("#btnclear").prop('type', 'button');
1388

    
1389
	// Search for a term in the entry name and/or dn
1390
	$("#btnsearch").click(function() {
1391
		var searchstr = $('#searchstr').val().toLowerCase();
1392
		var table = $("table tbody");
1393
		var where = $('#where').val();
1394

    
1395
		table.find('tr').each(function (i) {
1396
			var $tds = $(this).find('td'),
1397
				shortname = $tds.eq(0).text().trim().toLowerCase(),
1398
				dn = $tds.eq(2).text().trim().toLowerCase();
1399

    
1400
			regexp = new RegExp(searchstr);
1401
			if (searchstr.length > 0) {
1402
				if (!(regexp.test(shortname) && (where != 1)) && !(regexp.test(dn) && (where != 0))) {
1403
					$(this).hide();
1404
				} else {
1405
					$(this).show();
1406
				}
1407
			} else {
1408
				$(this).show();	// A blank search string shows all
1409
			}
1410
		});
1411
	});
1412

    
1413
	// Clear the search term and unhide all rows (that were hidden during a previous search)
1414
	$("#btnclear").click(function() {
1415
		var table = $("table tbody");
1416

    
1417
		$('#searchstr').val("");
1418

    
1419
		table.find('tr').each(function (i) {
1420
			$(this).show();
1421
		});
1422
	});
1423

    
1424
	// Hitting the enter key will do the same as clicking the search button
1425
	$("#searchstr").on("keyup", function (event) {
1426
		if (event.keyCode == 13) {
1427
			$("#btnsearch").get(0).click();
1428
		}
1429
	});
1430
});
1431
//]]>
1432
</script>
1433
<?php
1434
	include("foot.inc");
1435
	exit;
1436
}
1437

    
1438

    
1439
?>
1440
<script type="text/javascript">
1441
//<![CDATA[
1442
events.push(function() {
1443

    
1444
<?php if ($internal_ca_count): ?>
1445
	function internalca_change() {
1446

    
1447
		caref = $('#caref').val();
1448

    
1449
		switch (caref) {
1450
<?php
1451
			foreach ($a_ca as $ca):
1452
				if (!$ca['prv']) {
1453
					continue;
1454
				}
1455

    
1456
				$subject = @cert_get_subject_hash($ca['crt']);
1457
				if (!is_array($subject) || empty($subject)) {
1458
					continue;
1459
				}
1460
?>
1461
				case "<?=$ca['refid'];?>":
1462
					$('#dn_country').val(<?=json_encode(cert_escape_x509_chars($subject['C'], true));?>);
1463
					$('#dn_state').val(<?=json_encode(cert_escape_x509_chars($subject['ST'], true));?>);
1464
					$('#dn_city').val(<?=json_encode(cert_escape_x509_chars($subject['L'], true));?>);
1465
					$('#dn_organization').val(<?=json_encode(cert_escape_x509_chars($subject['O'], true));?>);
1466
					$('#dn_organizationalunit').val(<?=json_encode(cert_escape_x509_chars($subject['OU'], true));?>);
1467
					break;
1468
<?php
1469
			endforeach;
1470
?>
1471
		}
1472
	}
1473

    
1474
	function set_csr_ro() {
1475
		var newcsr = ($('#csrtosign').val() == "new");
1476

    
1477
		$('#csrpaste').attr('readonly', !newcsr);
1478
		$('#keypaste').attr('readonly', !newcsr);
1479
		setRequired('csrpaste', newcsr);
1480
	}
1481

    
1482
	function check_lifetime() {
1483
		var maxserverlife = <?= $cert_strict_values['max_server_cert_lifetime'] ?>;
1484
		var ltid = '#lifetime';
1485
		if ($('#method').val() == "sign") {
1486
			ltid = '#csrsign_lifetime';
1487
		}
1488
		if (($('#type').val() == "server") && (parseInt($(ltid).val()) > maxserverlife)) {
1489
			$(ltid).parent().parent().removeClass("text-normal").addClass("text-warning");
1490
			$(ltid).removeClass("text-normal").addClass("text-warning");
1491
		} else {
1492
			$(ltid).parent().parent().removeClass("text-warning").addClass("text-normal");
1493
			$(ltid).removeClass("text-warning").addClass("text-normal");
1494
		}
1495
	}
1496
	function check_keylen() {
1497
		var min_keylen = <?= $cert_strict_values['min_private_key_bits'] ?>;
1498
		var klid = '#keylen';
1499
		if ($('#method').val() == "external") {
1500
			klid = '#csr_keylen';
1501
		}
1502
		/* Color the Parent/Label */
1503
		if (parseInt($(klid).val()) < min_keylen) {
1504
			$(klid).parent().parent().removeClass("text-normal").addClass("text-warning");
1505
		} else {
1506
			$(klid).parent().parent().removeClass("text-warning").addClass("text-normal");
1507
		}
1508
		/* Color individual options */
1509
		$(klid + " option").filter(function() {
1510
			return parseInt($(this).val()) < min_keylen;
1511
		}).removeClass("text-normal").addClass("text-warning").siblings().removeClass("text-warning").addClass("text-normal");
1512
	}
1513

    
1514
	function check_digest() {
1515
		var weak_algs = <?= json_encode($cert_strict_values['digest_blacklist']) ?>;
1516
		var daid = '#digest_alg';
1517
		if ($('#method').val() == "external") {
1518
			daid = '#csr_digest_alg';
1519
		} else if ($('#method').val() == "sign") {
1520
			daid = '#csrsign_digest_alg';
1521
		}
1522
		/* Color the Parent/Label */
1523
		if (jQuery.inArray($(daid).val(), weak_algs) > -1) {
1524
			$(daid).parent().parent().removeClass("text-normal").addClass("text-warning");
1525
		} else {
1526
			$(daid).parent().parent().removeClass("text-warning").addClass("text-normal");
1527
		}
1528
		/* Color individual options */
1529
		$(daid + " option").filter(function() {
1530
			return (jQuery.inArray($(this).val(), weak_algs) > -1);
1531
		}).removeClass("text-normal").addClass("text-warning").siblings().removeClass("text-warning").addClass("text-normal");
1532
	}
1533

    
1534
	// ---------- Click checkbox handlers ---------------------------------------------------------
1535

    
1536
	$('#type').on('change', function() {
1537
		check_lifetime();
1538
	});
1539
	$('#method').on('change', function() {
1540
		check_lifetime();
1541
		check_keylen();
1542
		check_digest();
1543
	});
1544
	$('#lifetime').on('change', function() {
1545
		check_lifetime();
1546
	});
1547
	$('#csrsign_lifetime').on('change', function() {
1548
		check_lifetime();
1549
	});
1550

    
1551
	$('#keylen').on('change', function() {
1552
		check_keylen();
1553
	});
1554
	$('#csr_keylen').on('change', function() {
1555
		check_keylen();
1556
	});
1557

    
1558
	$('#digest_alg').on('change', function() {
1559
		check_digest();
1560
	});
1561
	$('#csr_digest_alg').on('change', function() {
1562
		check_digest();
1563
	});
1564

    
1565
	$('#caref').on('change', function() {
1566
		internalca_change();
1567
	});
1568

    
1569
	$('#csrtosign').change(function () {
1570
		set_csr_ro();
1571
	});
1572

    
1573
	function change_keytype() {
1574
		hideClass('rsakeys', ($('#keytype').val() != 'RSA'));
1575
		hideClass('ecnames', ($('#keytype').val() != 'ECDSA'));
1576
	}
1577

    
1578
	$('#keytype').change(function () {
1579
		change_keytype();
1580
	});
1581

    
1582
	function change_csrkeytype() {
1583
		hideClass('csr_rsakeys', ($('#csr_keytype').val() != 'RSA'));
1584
		hideClass('csr_ecnames', ($('#csr_keytype').val() != 'ECDSA'));
1585
	}
1586

    
1587
	$('#csr_keytype').change(function () {
1588
		change_csrkeytype();
1589
	});
1590

    
1591
	// ---------- On initial page load ------------------------------------------------------------
1592

    
1593
	internalca_change();
1594
	set_csr_ro();
1595
	change_keytype();
1596
	change_csrkeytype();
1597
	check_lifetime();
1598
	check_keylen();
1599
	check_digest();
1600

    
1601
	// Suppress "Delete row" button if there are fewer than two rows
1602
	checkLastRow();
1603

    
1604

    
1605
<?php endif; ?>
1606

    
1607

    
1608
});
1609
//]]>
1610
</script>
1611
<?php
1612
include('foot.inc');
(193-193/227)