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
	$section->addInput(new Form_Input(
784
		'exportpass',
785
		'Export Password',
786
		'password',
787
		null,
788
		['placeholder' => gettext('Export Password'), 'autocomplete' => 'new-password']
789
	))->setHelp('Enter the password to use when using the export buttons below (not stored)')->addClass('toggle-edit collapse');
790

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

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

    
809
			$allCas[ $ca['refid'] ] = $ca['descr'];
810
		}
811

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1018
	$existCerts = array();
1019

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

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

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

    
1042
		$existCerts[ $cert['refid'] ] = $cert['descr'];
1043
	}
1044

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

    
1052
	$form->add($section);
1053

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

    
1057
	$section->addInput(new Form_StaticText(
1058
		gettext('Attribute Notes'),
1059
		'<span class="help-block">'.
1060
		gettext('The following attributes are added to certificates and ' .
1061
		'requests when they are created or signed. These attributes behave ' .
1062
		'differently depending on the selected mode.') .
1063
		'<br/><br/>' .
1064
		'<span class="toggle-internal collapse">' . gettext('For Internal Certificates, these attributes are added directly to the certificate as shown.') . '</span>' .
1065
		'<span class="toggle-external collapse">' .
1066
		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. ') .
1067
		'<br/><br/>' .
1068
		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>' .
1069
		'<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>' .
1070
		'</span>'
1071
	));
1072

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

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

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

    
1091
	foreach ($pconfig['altnames']['item'] as $item) {
1092

    
1093
		$group = new Form_Group($counter == 0 ? 'Alternative Names':'');
1094

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

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

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

    
1116
		$group->addClass('repeatable');
1117

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

    
1123
		$section->add($group);
1124

    
1125
		$counter++;
1126
	}
1127

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

    
1135
	$form->add($section);
1136

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

    
1152
	print $form;
1153

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

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

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

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

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

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

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

    
1198
	$form->add($section);
1199

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

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

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

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

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

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

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

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

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

    
1381
events.push(function() {
1382

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

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

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

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

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

    
1415
		$('#searchstr').val("");
1416

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

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

    
1436

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

    
1442
<?php if ($internal_ca_count): ?>
1443
	function internalca_change() {
1444

    
1445
		caref = $('#caref').val();
1446

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

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

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

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

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

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

    
1532
	// ---------- Click checkbox handlers ---------------------------------------------------------
1533

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

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

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

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

    
1567
	$('#csrtosign').change(function () {
1568
		set_csr_ro();
1569
	});
1570

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

    
1576
	$('#keytype').change(function () {
1577
		change_keytype();
1578
	});
1579

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

    
1585
	$('#csr_keytype').change(function () {
1586
		change_csrkeytype();
1587
	});
1588

    
1589
	// ---------- On initial page load ------------------------------------------------------------
1590

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

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

    
1602

    
1603
<?php endif; ?>
1604

    
1605

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