Project

General

Profile

Download (53.6 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-2022 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
			if ((strlen($_POST['exportpass']) < 4) or (strlen($_POST['exportpass']) > 1023)) {
167
				$savemsg = gettext("Export password must be in 4 to 1023 characters.");
168
				$class = 'danger';
169
				break;
170
			} else {
171
				$res_key = openssl_pkey_get_private($keyout);
172
				if ($res_key) {
173
					$args = array('encrypt_key_cipher' => OPENSSL_CIPHER_AES_256_CBC);
174
					openssl_pkey_export($res_key, $keyout, $_POST['exportpass'], $args);
175
				} else {
176
					$savemsg = gettext("Unable to export password-protected private key.");
177
					$class = 'danger';
178
				}
179
			}
180
		}
181
		if (!empty($keyout)) {
182
			send_user_download('data', $keyout, "{$thiscert['descr']}.key");
183
		}
184
		break;
185
	case 'p12':
186
		/* Exporting a PKCS#12 file containing the certificate, key, and (if present) CA */
187
		if (isset($_POST['exportpass']) && !empty($_POST['exportpass'])) {
188
			if ((strlen($_POST['exportpass']) < 4) or (strlen($_POST['exportpass']) > 1023)) {
189
				$savemsg = gettext("Export password must be in 4 to 1023 characters.");
190
				$class = 'danger';
191
				break;
192
			} else {
193
				$password = $_POST['exportpass'];
194
			}
195
		} else {
196
			$password = null;
197
		}
198
		$args = array();
199
		$args['friendly_name'] = $thiscert['descr'];
200
		$args['encrypt_key_cipher'] = OPENSSL_CIPHER_AES_256_CBC;
201
		$ca = lookup_ca($thiscert['caref']);
202
		if ($ca) {
203
			/* If the CA can be found, then add the CA to the container */
204
			$args['extracerts'] = openssl_x509_read(base64_decode($ca['crt']));
205
		}
206
		$res_crt = openssl_x509_read(base64_decode($thiscert['crt']));
207
		$res_key = openssl_pkey_get_private(base64_decode($thiscert['prv']));
208
		$exp_data = "";
209
		openssl_pkcs12_export($res_crt, $exp_data, $res_key, $password, $args);
210
		send_user_download('data', $exp_data, "{$thiscert['descr']}.p12");
211
		break;
212
	default:
213
		break;
214
}
215

    
216
if ($_POST['save'] == gettext("Save")) {
217
	/* Creating a new entry */
218
	$input_errors = array();
219
	$pconfig = $_POST;
220

    
221
	switch ($pconfig['method']) {
222
		case 'sign':
223
			$reqdfields = explode(" ",
224
				"descr catosignwith");
225
			$reqdfieldsn = array(
226
				gettext("Descriptive name"),
227
				gettext("CA to sign with"));
228

    
229
			if (($_POST['csrtosign'] === "new") &&
230
			    ((!strstr($_POST['csrpaste'], "BEGIN CERTIFICATE REQUEST") || !strstr($_POST['csrpaste'], "END CERTIFICATE REQUEST")) &&
231
			    (!strstr($_POST['csrpaste'], "BEGIN NEW CERTIFICATE REQUEST") || !strstr($_POST['csrpaste'], "END NEW CERTIFICATE REQUEST")))) {
232
				$input_errors[] = gettext("This signing request does not appear to be valid.");
233
			}
234

    
235
			if ( (($_POST['csrtosign'] === "new") && (strlen($_POST['keypaste']) > 0)) && 
236
			    ((!strstr($_POST['keypaste'], "BEGIN PRIVATE KEY") && !strstr($_POST['keypaste'], "BEGIN EC PRIVATE KEY")) || 
237
			    (strstr($_POST['keypaste'], "BEGIN PRIVATE KEY") && !strstr($_POST['keypaste'], "END PRIVATE KEY")) ||
238
			    (strstr($_POST['keypaste'], "BEGIN EC PRIVATE KEY") && !strstr($_POST['keypaste'], "END EC PRIVATE KEY")))) {
239
				$input_errors[] = gettext("This private does not appear to be valid.");
240
				$input_errors[] = gettext("Key data field should be blank, or a valid x509 private key");
241
			}
242

    
243
			if ($_POST['lifetime'] > $max_lifetime) {
244
				$input_errors[] = gettext("Lifetime is longer than the maximum allowed value. Use a shorter lifetime.");
245
			}
246
			break;
247
		case 'edit':
248
		case 'import':
249
			$pkcs12_data = '';
250
			if ($_POST['import_type'] == 'x509') {
251
				$reqdfields = explode(" ",
252
					"descr cert");
253
				$reqdfieldsn = array(
254
					gettext("Descriptive name"),
255
					gettext("Certificate data"));
256
				if ($_POST['cert'] && (!strstr($_POST['cert'], "BEGIN CERTIFICATE") || !strstr($_POST['cert'], "END CERTIFICATE"))) {
257
					$input_errors[] = gettext("This certificate does not appear to be valid.");
258
				}
259

    
260
				if ($_POST['key'] && (cert_get_publickey($_POST['cert'], false) != cert_get_publickey($_POST['key'], false, 'prv'))) {
261
					$input_errors[] = gettext("The submitted private key does not match the submitted certificate data.");
262
				}
263
			} else {
264
				$reqdfields = array('descr');
265
				$reqdfieldsn = array(gettext("Descriptive name"));
266
				if (!empty($_FILES['pkcs12_cert']) && is_uploaded_file($_FILES['pkcs12_cert']['tmp_name'])) {
267
					$pkcs12_file = file_get_contents($_FILES['pkcs12_cert']['tmp_name']);
268
					if (!openssl_pkcs12_read($pkcs12_file, $pkcs12_data, $_POST['pkcs12_pass'])) {
269
						$input_errors[] = gettext("The submitted password does not unlock the submitted PKCS #12 certificate.");
270
					}
271
				} else {
272
					$input_errors[] = gettext("A PKCS #12 certificate store was not uploaded.");
273
				}
274
			}
275
			break;
276
		case 'internal':
277
			$reqdfields = explode(" ",
278
				"descr caref keylen ecname keytype type lifetime dn_commonname");
279
			$reqdfieldsn = array(
280
				gettext("Descriptive name"),
281
				gettext("Certificate authority"),
282
				gettext("Key length"),
283
				gettext("Elliptic Curve Name"),
284
				gettext("Key type"),
285
				gettext("Certificate Type"),
286
				gettext("Lifetime"),
287
				gettext("Common Name"));
288
			if ($_POST['lifetime'] > $max_lifetime) {
289
				$input_errors[] = gettext("Lifetime is longer than the maximum allowed value. Use a shorter lifetime.");
290
			}
291
			break;
292
		case 'external':
293
			$reqdfields = explode(" ",
294
				"descr csr_keylen csr_ecname csr_keytype csr_dn_commonname");
295
			$reqdfieldsn = array(
296
				gettext("Descriptive name"),
297
				gettext("Key length"),
298
				gettext("Elliptic Curve Name"),
299
				gettext("Key type"),
300
				gettext("Common Name"));
301
			break;
302
		case 'existing':
303
			$reqdfields = array("certref");
304
			$reqdfieldsn = array(gettext("Existing Certificate Choice"));
305
			break;
306
		default:
307
			break;
308
	}
309

    
310
	$altnames = array();
311
	do_input_validation($_POST, $reqdfields, $reqdfieldsn, $input_errors);
312

    
313
	if (!in_array($pconfig['method'], array('edit', 'import', 'existing'))) {
314
		/* subjectAltNames */
315
		$san_typevar = 'altname_type';
316
		$san_valuevar = 'altname_value';
317
		// This is just the blank alternate name that is added for display purposes. We don't want to validate/save it
318
		if ($_POST["{$san_valuevar}0"] == "") {
319
			unset($_POST["{$san_typevar}0"]);
320
			unset($_POST["{$san_valuevar}0"]);
321
		}
322
		foreach ($_POST as $key => $value) {
323
			$entry = '';
324
			if (!substr_compare($san_typevar, $key, 0, strlen($san_typevar))) {
325
				$entry = substr($key, strlen($san_typevar));
326
				$field = 'type';
327
			} elseif (!substr_compare($san_valuevar, $key, 0, strlen($san_valuevar))) {
328
				$entry = substr($key, strlen($san_valuevar));
329
				$field = 'value';
330
			}
331

    
332
			if (ctype_digit($entry)) {
333
				$entry++;	// Pre-bootstrap code is one-indexed, but the bootstrap code is 0-indexed
334
				$altnames[$entry][$field] = $value;
335
			}
336
		}
337

    
338
		$pconfig['altnames']['item'] = $altnames;
339

    
340
		/* Input validation for subjectAltNames */
341
		foreach ($altnames as $idx => $altname) {
342
			switch ($altname['type']) {
343
				case "DNS":
344
					if (!is_hostname($altname['value'], true) || is_ipaddr($altname['value'])) {
345
						$input_errors[] = gettext("DNS subjectAltName values must be valid hostnames, FQDNs or wildcard domains.");
346
					}
347
					break;
348
				case "IP":
349
					if (!is_ipaddr($altname['value'])) {
350
						$input_errors[] = gettext("IP subjectAltName values must be valid IP Addresses");
351
					}
352
					break;
353
				case "email":
354
					if (empty($altname['value'])) {
355
						$input_errors[] = gettext("An e-mail address must be provided for this type of subjectAltName");
356
					}
357
					if (preg_match("/[\!\#\$\%\^\(\)\~\?\>\<\&\/\\\,\"\']/", $altname['value'])) {
358
						$input_errors[] = gettext("The e-mail provided in a subjectAltName contains invalid characters.");
359
					}
360
					break;
361
				case "URI":
362
					/* Close enough? */
363
					if (!is_URL($altname['value'])) {
364
						$input_errors[] = gettext("URI subjectAltName types must be a valid URI");
365
					}
366
					break;
367
				default:
368
					$input_errors[] = gettext("Unrecognized subjectAltName type.");
369
			}
370
		}
371

    
372
		/* Make sure we do not have invalid characters in the fields for the certificate */
373
		if (preg_match("/[\?\>\<\&\/\\\"\']/", $_POST['descr'])) {
374
			$input_errors[] = gettext("The field 'Descriptive Name' contains invalid characters.");
375
		}
376
		$pattern = '/[^a-zA-Z0-9\ \'\/~`\!@#\$%\^&\*\(\)_\-\+=\{\}\[\]\|;:"\<\>,\.\?\\\]/';
377
		if (!empty($_POST['dn_commonname']) && preg_match($pattern, $_POST['dn_commonname'])) {
378
			$input_errors[] = gettext("The field 'Common Name' contains invalid characters.");
379
		}
380
		if (!empty($_POST['dn_state']) && preg_match($pattern, $_POST['dn_state'])) {
381
			$input_errors[] = gettext("The field 'State or Province' contains invalid characters.");
382
		}
383
		if (!empty($_POST['dn_city']) && preg_match($pattern, $_POST['dn_city'])) {
384
			$input_errors[] = gettext("The field 'City' contains invalid characters.");
385
		}
386
		if (!empty($_POST['dn_organization']) && preg_match($pattern, $_POST['dn_organization'])) {
387
			$input_errors[] = gettext("The field 'Organization' contains invalid characters.");
388
		}
389
		if (!empty($_POST['dn_organizationalunit']) && preg_match($pattern, $_POST['dn_organizationalunit'])) {
390
			$input_errors[] = gettext("The field 'Organizational Unit' contains invalid characters.");
391
		}
392

    
393
		switch ($pconfig['method']) {
394
			case "internal":
395
				if (isset($_POST["keytype"]) && !in_array($_POST["keytype"], $cert_keytypes)) {
396
					$input_errors[] = gettext("Please select a valid Key Type.");
397
				}
398
				if (isset($_POST["keylen"]) && !in_array($_POST["keylen"], $cert_keylens)) {
399
					$input_errors[] = gettext("Please select a valid Key Length.");
400
				}
401
				if (isset($_POST["ecname"]) && !in_array($_POST["ecname"], array_keys($openssl_ecnames))) {
402
					$input_errors[] = gettext("Please select a valid Elliptic Curve Name.");
403
				}
404
				if (!in_array($_POST["digest_alg"], $openssl_digest_algs)) {
405
					$input_errors[] = gettext("Please select a valid Digest Algorithm.");
406
				}
407
				break;
408
			case "external":
409
				if (isset($_POST["csr_keytype"]) && !in_array($_POST["csr_keytype"], $cert_keytypes)) {
410
					$input_errors[] = gettext("Please select a valid Key Type.");
411
				}
412
				if (isset($_POST["csr_keylen"]) && !in_array($_POST["csr_keylen"], $cert_keylens)) {
413
					$input_errors[] = gettext("Please select a valid Key Length.");
414
				}
415
				if (isset($_POST["csr_ecname"]) && !in_array($_POST["csr_ecname"], array_keys($openssl_ecnames))) {
416
					$input_errors[] = gettext("Please select a valid Elliptic Curve Name.");
417
				}
418
				if (!in_array($_POST["csr_digest_alg"], $openssl_digest_algs)) {
419
					$input_errors[] = gettext("Please select a valid Digest Algorithm.");
420
				}
421
				break;
422
			case "sign":
423
				if (!in_array($_POST["csrsign_digest_alg"], $openssl_digest_algs)) {
424
					$input_errors[] = gettext("Please select a valid Digest Algorithm.");
425
				}
426
				break;
427
			default:
428
				break;
429
		}
430
	}
431

    
432
	/* save modifications */
433
	if (!$input_errors) {
434
		$old_err_level = error_reporting(0); /* otherwise openssl_ functions throw warnings directly to a page breaking menu tabs */
435

    
436
		if (isset($id) && $thiscert) {
437
			$cert = $thiscert;
438
		} else {
439
			$cert = array();
440
			$cert['refid'] = uniqid();
441
		}
442

    
443
		$cert['descr'] = $pconfig['descr'];
444

    
445
		switch($pconfig['method']) {
446
			case 'existing':
447
				/* Add an existing certificate to a user */
448
				$ucert = lookup_cert($pconfig['certref']);
449
				if ($ucert && $a_user) {
450
					$a_user[$userid]['cert'][] = $ucert['refid'];
451
					$savemsg = sprintf(gettext("Added certificate %s to user %s"), $ucert['descr'], $a_user[$userid]['name']);
452
				}
453
				unset($cert);
454
				break;
455
			case 'sign':
456
				/* Sign a CSR */
457
				$csrid = lookup_cert($pconfig['csrtosign']);
458
				$ca = & lookup_ca($pconfig['catosignwith']);
459
				// Read the CSR from $config, or if a new one, from the textarea
460
				if ($pconfig['csrtosign'] === "new") {
461
					$csr = $pconfig['csrpaste'];
462
				} else {
463
					$csr = base64_decode($csrid['csr']);
464
				}
465
				if (count($altnames)) {
466
					foreach ($altnames as $altname) {
467
						$altnames_tmp[] = "{$altname['type']}:" . $altname['value'];
468
					}
469
					$altname_str = implode(",", $altnames_tmp);
470
				}
471
				$n509 = csr_sign($csr, $ca, $pconfig['csrsign_lifetime'], $pconfig['type'], $altname_str, $pconfig['csrsign_digest_alg']);
472
				if ($n509) {
473
					// Gather the details required to save the new cert
474
					$newcert = array();
475
					$newcert['refid'] = uniqid();
476
					$newcert['caref'] = $pconfig['catosignwith'];
477
					$newcert['descr'] = $pconfig['descr'];
478
					$newcert['type'] = $pconfig['type'];
479
					$newcert['crt'] = base64_encode($n509);
480
					if ($pconfig['csrtosign'] === "new") {
481
						$newcert['prv'] = base64_encode($pconfig['keypaste']);
482
					} else {
483
						$newcert['prv'] = $csrid['prv'];
484
					}
485
					// Add it to the config file
486
					$config['cert'][] = $newcert;
487
					$savemsg = sprintf(gettext("Signed certificate %s"), $newcert['descr']);
488
				}
489
				unset($cert);
490
				break;
491
			case 'edit':
492
				cert_import($cert, $pconfig['cert'], $pconfig['key']);
493
				$savemsg = sprintf(gettext("Edited certificate %s"), $cert['descr']);
494
				break;
495
			case 'import':
496
				/* Import an external certificate+key */
497
				if ($pkcs12_data) {
498
					$pconfig['cert'] = $pkcs12_data['cert'];
499
					$pconfig['key'] = $pkcs12_data['pkey'];
500
					if ($_POST['pkcs12_intermediate'] && is_array($pkcs12_data['extracerts'])) {
501
						foreach ($pkcs12_data['extracerts'] as $intermediate) {
502
							$int_data = openssl_x509_parse($intermediate);
503
							if (!$int_data) continue;
504
							$cn = $int_data['subject']['CN'];
505
							$int_ca = array('descr' => $cn, 'refid' => uniqid());
506
							if (ca_import($int_ca, $intermediate)) {
507
								$a_ca[] = $int_ca;
508
							}
509
						}
510
					}
511
				}
512
				cert_import($cert, $pconfig['cert'], $pconfig['key']);
513
				$savemsg = sprintf(gettext("Imported certificate %s"), $cert['descr']);
514
				break;
515
			case 'internal':
516
				/* Create an internal certificate */
517
				$dn = array('commonName' => $pconfig['dn_commonname']);
518
				if (!empty($pconfig['dn_country'])) {
519
					$dn['countryName'] = $pconfig['dn_country'];
520
				}
521
				if (!empty($pconfig['dn_state'])) {
522
					$dn['stateOrProvinceName'] = $pconfig['dn_state'];
523
				}
524
				if (!empty($pconfig['dn_city'])) {
525
					$dn['localityName'] = $pconfig['dn_city'];
526
				}
527
				if (!empty($pconfig['dn_organization'])) {
528
					$dn['organizationName'] = $pconfig['dn_organization'];
529
				}
530
				if (!empty($pconfig['dn_organizationalunit'])) {
531
					$dn['organizationalUnitName'] = $pconfig['dn_organizationalunit'];
532
				}
533
				$altnames_tmp = array();
534
				$cn_altname = cert_add_altname_type($pconfig['dn_commonname']);
535
				if (!empty($cn_altname)) {
536
					$altnames_tmp[] = $cn_altname;
537
				}
538
				if (count($altnames)) {
539
					foreach ($altnames as $altname) {
540
						// The CN is added as a SAN automatically, do not add it again.
541
						if ($altname['value'] != $pconfig['dn_commonname']) {
542
							$altnames_tmp[] = "{$altname['type']}:" . $altname['value'];
543
						}
544
					}
545
				}
546
				if (!empty($altnames_tmp)) {
547
					$dn['subjectAltName'] = implode(",", $altnames_tmp);
548
				}
549
				if (!cert_create($cert, $pconfig['caref'], $pconfig['keylen'], $pconfig['lifetime'], $dn, $pconfig['type'], $pconfig['digest_alg'], $pconfig['keytype'], $pconfig['ecname'])) {
550
					$input_errors = array();
551
					while ($ssl_err = openssl_error_string()) {
552
						if (strpos($ssl_err, 'NCONF_get_string:no value') === false) {
553
							$input_errors[] = sprintf(gettext("OpenSSL Library Error: %s"), $ssl_err);
554
						}
555
					}
556
				}
557
				$savemsg = sprintf(gettext("Created internal certificate %s"), $cert['descr']);
558
				break;
559
			case 'external':
560
				/* Create a certificate signing request */
561
				$dn = array('commonName' => $pconfig['csr_dn_commonname']);
562
				if (!empty($pconfig['csr_dn_country'])) {
563
					$dn['countryName'] = $pconfig['csr_dn_country'];
564
				}
565
				if (!empty($pconfig['csr_dn_state'])) {
566
					$dn['stateOrProvinceName'] = $pconfig['csr_dn_state'];
567
				}
568
				if (!empty($pconfig['csr_dn_city'])) {
569
					$dn['localityName'] = $pconfig['csr_dn_city'];
570
				}
571
				if (!empty($pconfig['csr_dn_organization'])) {
572
					$dn['organizationName'] = $pconfig['csr_dn_organization'];
573
				}
574
				if (!empty($pconfig['csr_dn_organizationalunit'])) {
575
					$dn['organizationalUnitName'] = $pconfig['csr_dn_organizationalunit'];
576
				}
577
				$altnames_tmp = array();
578
				$cn_altname = cert_add_altname_type($pconfig['csr_dn_commonname']);
579
				if (!empty($cn_altname)) {
580
					$altnames_tmp[] = $cn_altname;
581
				}
582
				if (count($altnames)) {
583
					foreach ($altnames as $altname) {
584
						// The CN is added as a SAN automatically, do not add it again.
585
						if ($altname['value'] != $pconfig['csr_dn_commonname']) {
586
							$altnames_tmp[] = "{$altname['type']}:" . $altname['value'];
587
						}
588
					}
589
				}
590
				if (!empty($altnames_tmp)) {
591
					$dn['subjectAltName'] = implode(",", $altnames_tmp);
592
				}
593
				if (!csr_generate($cert, $pconfig['csr_keylen'], $dn, $pconfig['type'], $pconfig['csr_digest_alg'], $pconfig['csr_keytype'], $pconfig['csr_ecname'])) {
594
					$input_errors = array();
595
					while ($ssl_err = openssl_error_string()) {
596
						if (strpos($ssl_err, 'NCONF_get_string:no value') === false) {
597
							$input_errors[] = sprintf(gettext("OpenSSL Library Error: %s"), $ssl_err);
598
						}
599
					}
600
				}
601
				$savemsg = sprintf(gettext("Created certificate signing request %s"), $cert['descr']);
602
				break;
603
			default:
604
				break;
605
		}
606
		error_reporting($old_err_level);
607

    
608
		if (isset($id) && $thiscert) {
609
			$thiscert = $cert;
610
		} elseif ($cert) {
611
			$a_cert[] = $cert;
612
		}
613

    
614
		if (isset($a_user) && isset($userid)) {
615
			$a_user[$userid]['cert'][] = $cert['refid'];
616
		}
617

    
618
		if (!$input_errors) {
619
			write_config($savemsg);
620
		}
621

    
622
		if ((isset($userid) && is_numeric($userid)) && !$input_errors) {
623
			post_redirect("system_usermanager.php", array('act' => 'edit', 'userid' => $userid));
624
			exit;
625
		}
626
	}
627
} elseif ($_POST['save'] == gettext("Update")) {
628
	/* Updating a certificate signing request */
629
	unset($input_errors);
630
	$pconfig = $_POST;
631

    
632
	/* input validation */
633
	$reqdfields = explode(" ", "descr cert");
634
	$reqdfieldsn = array(
635
		gettext("Descriptive name"),
636
		gettext("Final Certificate data"));
637

    
638
	do_input_validation($_POST, $reqdfields, $reqdfieldsn, $input_errors);
639

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

    
644
	$mod_csr = cert_get_publickey($pconfig['csr'], false, 'csr');
645
	$mod_cert = cert_get_publickey($pconfig['cert'], false);
646

    
647
	if (strcmp($mod_csr, $mod_cert)) {
648
		// simply: if the moduli don't match, then the private key and public key won't match
649
		$input_errors[] = gettext("The certificate public key does not match the signing request public key.");
650
		$subject_mismatch = true;
651
	}
652

    
653
	/* save modifications */
654
	if (!$input_errors) {
655
		$cert = $thiscert;
656
		$cert['descr'] = $pconfig['descr'];
657
		csr_complete($cert, $pconfig['cert']);
658
		$thiscert = $cert;
659
		$savemsg = sprintf(gettext("Updated certificate signing request %s"), $pconfig['descr']);
660
		write_config($savemsg);
661
		pfSenseHeader("system_certmanager.php");
662
	}
663
}
664

    
665
$pgtitle = array(gettext("System"), gettext("Certificate Manager"), gettext("Certificates"));
666
$pglinks = array("", "system_camanager.php", "system_certmanager.php");
667

    
668
if (($act == "new" || ($_POST['save'] == gettext("Save") && $input_errors)) ||
669
    ($act == "csr" || ($_POST['save'] == gettext("Update") && $input_errors))) {
670
	$pgtitle[] = gettext('Edit');
671
	$pglinks[] = "@self";
672
}
673
include("head.inc");
674

    
675
if ($input_errors) {
676
	print_input_errors($input_errors);
677
}
678

    
679
if ($savemsg) {
680
	print_info_box($savemsg, $class);
681
}
682

    
683
$tab_array = array();
684
$tab_array[] = array(gettext("CAs"), false, "system_camanager.php");
685
$tab_array[] = array(gettext("Certificates"), true, "system_certmanager.php");
686
$tab_array[] = array(gettext("Certificate Revocation"), false, "system_crlmanager.php");
687
display_top_tabs($tab_array);
688

    
689
if (in_array($act, array('new', 'edit')) || (($_POST['save'] == gettext("Save")) && $input_errors)) {
690
	$form = new Form();
691
	$form->setAction('system_certmanager.php')->setMultipartEncoding();
692

    
693
	if (isset($userid) && $a_user) {
694
		$form->addGlobal(new Form_Input(
695
			'userid',
696
			null,
697
			'hidden',
698
			$userid
699
		));
700
	}
701

    
702
	if (isset($id) && $thiscert) {
703
		$form->addGlobal(new Form_Input(
704
			'id',
705
			null,
706
			'hidden',
707
			$id
708
		));
709
	}
710

    
711
	switch ($act) {
712
		case 'edit':
713
			$maintitle = gettext('Edit an Existing Certificate');
714
			break;
715
		case 'new':
716
		default:
717
			$maintitle = gettext('Add/Sign a New Certificate');
718
			break;
719
	}
720

    
721
	$section = new Form_Section($maintitle);
722

    
723
	if (!isset($id) || ($act == 'edit')) {
724
		$section->addInput(new Form_Select(
725
			'method',
726
			'*Method',
727
			$pconfig['method'],
728
			$cert_methods
729
		))->toggles();
730
	}
731

    
732
	$section->addInput(new Form_Input(
733
		'descr',
734
		'*Descriptive name',
735
		'text',
736
		($a_user && empty($pconfig['descr'])) ? $a_user[$userid]['name'] : $pconfig['descr']
737
	))->addClass('toggle-internal toggle-import toggle-edit toggle-external toggle-sign toggle-existing collapse');
738

    
739
	if (!empty($pconfig['cert'])) {
740
		$section->addInput(new Form_StaticText(
741
			"Subject",
742
			htmlspecialchars(cert_get_subject($pconfig['cert'], false))
743
		))->addClass('toggle-edit collapse');
744
	}
745

    
746
	$form->add($section);
747

    
748
	// Return an array containing the IDs od all CAs
749
	function list_cas() {
750
		global $a_ca;
751
		$allCas = array();
752

    
753
		foreach ($a_ca as $ca) {
754
			if ($ca['prv']) {
755
				$allCas[$ca['refid']] = $ca['descr'];
756
			}
757
		}
758

    
759
		return $allCas;
760
	}
761

    
762
	// Return an array containing the IDs od all CSRs
763
	function list_csrs() {
764
		global $config;
765
		$allCsrs = array();
766

    
767
		foreach ($config['cert'] as $cert) {
768
			if ($cert['csr']) {
769
				$allCsrs[$cert['refid']] = $cert['descr'];
770
			}
771
		}
772

    
773
		return ['new' => gettext('New CSR (Paste below)')] + $allCsrs;
774
	}
775

    
776
	$section = new Form_Section('Sign CSR');
777
	$section->addClass('toggle-sign collapse');
778

    
779
	$section->AddInput(new Form_Select(
780
		'catosignwith',
781
		'*CA to sign with',
782
		$pconfig['catosignwith'],
783
		list_cas()
784
	));
785

    
786
	$section->AddInput(new Form_Select(
787
		'csrtosign',
788
		'*CSR to sign',
789
		isset($pconfig['csrtosign']) ? $pconfig['csrtosign'] : 'new',
790
		list_csrs()
791
	));
792

    
793
	$section->addInput(new Form_Textarea(
794
		'csrpaste',
795
		'CSR data',
796
		$pconfig['csrpaste']
797
	))->setHelp('Paste a Certificate Signing Request in X.509 PEM format here.');
798

    
799
	$section->addInput(new Form_Textarea(
800
		'keypaste',
801
		'Key data',
802
		$pconfig['keypaste']
803
	))->setHelp('Optionally paste a private key here. The key will be associated with the newly signed certificate in %1$s', $g['product_label']);
804

    
805
	$section->addInput(new Form_Input(
806
		'csrsign_lifetime',
807
		'*Certificate Lifetime (days)',
808
		'number',
809
		$pconfig['csrsign_lifetime'] ? $pconfig['csrsign_lifetime']:$default_lifetime,
810
		['max' => $max_lifetime]
811
	))->setHelp('The length of time the signed certificate will be valid, in days. %1$s' .
812
		'Server certificates should not have a lifetime over %2$s days or some platforms ' .
813
		'may consider the certificate invalid.', '<br/>', $cert_strict_values['max_server_cert_lifetime']);
814
	$section->addInput(new Form_Select(
815
		'csrsign_digest_alg',
816
		'*Digest Algorithm',
817
		$pconfig['csrsign_digest_alg'],
818
		array_combine($openssl_digest_algs, $openssl_digest_algs)
819
	))->setHelp('The digest method used when the certificate is signed. %1$s' .
820
		'The best practice is to use an algorithm stronger than SHA1. '.
821
		'Some platforms may consider weaker digest algorithms invalid', '<br/>');
822

    
823
	$form->add($section);
824

    
825
	if ($act == 'edit') {
826
		$editimport = gettext("Edit Certificate");
827
	} else {
828
		$editimport = gettext("Import Certificate");
829
	}
830

    
831
	$section = new Form_Section($editimport);
832
	$section->addClass('toggle-import toggle-edit collapse');
833

    
834
	$group = new Form_Group('Certificate Type');
835

    
836
	$group->add(new Form_Checkbox(
837
		'import_type',
838
		'Certificate Type',
839
		'X.509 (PEM)',
840
		(!isset($pconfig['import_type']) || $pconfig['import_type'] == 'x509'),
841
		'x509'
842
	))->displayAsRadio()->addClass('import_type_toggle');
843

    
844
	$group->add(new Form_Checkbox(
845
		'import_type',
846
		'Certificate Type',
847
		'PKCS #12 (PFX)',
848
		(isset($pconfig['import_type']) && $pconfig['import_type'] == 'pkcs12'),
849
		'pkcs12'
850
	))->displayAsRadio()->addClass('import_type_toggle');
851

    
852
	$section->add($group);
853

    
854
	$section->addInput(new Form_Textarea(
855
		'cert',
856
		'*Certificate data',
857
		$pconfig['cert']
858
	))->setHelp('Paste a certificate in X.509 PEM format here.');
859

    
860
	$section->addInput(new Form_Textarea(
861
		'key',
862
		'Private key data',
863
		$pconfig['key']
864
	))->setHelp('Paste a private key in X.509 PEM format here. This field may remain empty in certain cases, such as when the private key is stored on a PKCS#11 token.');
865

    
866
	$section->addInput(new Form_Input(
867
		'pkcs12_cert',
868
		'PKCS #12 certificate',
869
		'file',
870
		$pconfig['pkcs12_cert']
871
	))->setHelp('Select a PKCS #12 certificate store.');
872

    
873
	$section->addInput(new Form_Input(
874
		'pkcs12_pass',
875
		'PKCS #12 certificate password',
876
		'password',
877
		$pconfig['pkcs12_pass']
878
	))->setHelp('Enter the password to unlock the PKCS #12 certificate store.');
879

    
880
	$section->addInput(new Form_Checkbox(
881
		'pkcs12_intermediate',
882
		'Intermediates',
883
		'Import intermediate CAs',
884
		isset($pconfig['pkcs12_intermediate'])
885
	))->setHelp('Import any intermediate certificate authorities found in the PKCS #12 certificate store.');
886

    
887
	if ($act == 'edit') {
888
		$section->addInput(new Form_Input(
889
			'exportpass',
890
			'Export Password',
891
			'password',
892
			null,
893
			['placeholder' => gettext('Export Password'), 'autocomplete' => 'new-password']
894
		))->setHelp('Enter the password to use when using the export buttons below (not stored)')->addClass('toggle-edit collapse');
895
	}
896

    
897
	$form->add($section);
898
	$section = new Form_Section('Internal Certificate');
899
	$section->addClass('toggle-internal collapse');
900

    
901
	if (!$internal_ca_count) {
902
		$section->addInput(new Form_StaticText(
903
			'*Certificate authority',
904
			gettext('No internal Certificate Authorities have been defined. ') .
905
			gettext('An internal CA must be defined in order to create an internal certificate. ') .
906
			sprintf(gettext('%1$sCreate%2$s an internal CA.'), '<a href="system_camanager.php?act=new&amp;method=internal"> ', '</a>')
907
		));
908
	} else {
909
		$allCas = array();
910
		foreach ($a_ca as $ca) {
911
			if (!$ca['prv']) {
912
				continue;
913
			}
914

    
915
			$allCas[ $ca['refid'] ] = $ca['descr'];
916
		}
917

    
918
		$section->addInput(new Form_Select(
919
			'caref',
920
			'*Certificate authority',
921
			$pconfig['caref'],
922
			$allCas
923
		));
924
	}
925

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

    
933
	$group = new Form_Group($i == 0 ? '*Key length':'');
934
	$group->addClass('rsakeys');
935
	$group->add(new Form_Select(
936
		'keylen',
937
		null,
938
		$pconfig['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('ecnames');
947
	$group->add(new Form_Select(
948
		'ecname',
949
		null,
950
		$pconfig['ecname'],
951
		$openssl_ecnames
952
	))->setHelp('Curves may not be compatible with all uses. Known compatible curve uses are denoted in brackets.');
953
	$section->add($group);
954

    
955
	$section->addInput(new Form_Select(
956
		'digest_alg',
957
		'*Digest Algorithm',
958
		$pconfig['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
		'lifetime',
966
		'*Lifetime (days)',
967
		'number',
968
		$pconfig['lifetime'],
969
		['max' => $max_lifetime]
970
	))->setHelp('The length of time the signed certificate will be valid, in days. %1$s' .
971
		'Server certificates should not have a lifetime over %2$s days or some platforms ' .
972
		'may consider the certificate invalid.', '<br/>', $cert_strict_values['max_server_cert_lifetime']);
973

    
974
	$section->addInput(new Form_Input(
975
		'dn_commonname',
976
		'*Common Name',
977
		'text',
978
		$pconfig['dn_commonname'],
979
		['placeholder' => 'e.g. www.example.com']
980
	));
981

    
982
	$section->addInput(new Form_StaticText(
983
		null,
984
		gettext('The following certificate subject components are optional and may be left blank.')
985
	));
986

    
987
	$section->addInput(new Form_Select(
988
		'dn_country',
989
		'Country Code',
990
		$pconfig['dn_country'],
991
		get_cert_country_codes()
992
	));
993

    
994
	$section->addInput(new Form_Input(
995
		'dn_state',
996
		'State or Province',
997
		'text',
998
		$pconfig['dn_state'],
999
		['placeholder' => 'e.g. Texas']
1000
	));
1001

    
1002
	$section->addInput(new Form_Input(
1003
		'dn_city',
1004
		'City',
1005
		'text',
1006
		$pconfig['dn_city'],
1007
		['placeholder' => 'e.g. Austin']
1008
	));
1009

    
1010
	$section->addInput(new Form_Input(
1011
		'dn_organization',
1012
		'Organization',
1013
		'text',
1014
		$pconfig['dn_organization'],
1015
		['placeholder' => 'e.g. My Company Inc']
1016
	));
1017

    
1018
	$section->addInput(new Form_Input(
1019
		'dn_organizationalunit',
1020
		'Organizational Unit',
1021
		'text',
1022
		$pconfig['dn_organizationalunit'],
1023
		['placeholder' => 'e.g. My Department Name (optional)']
1024
	));
1025

    
1026
	$form->add($section);
1027
	$section = new Form_Section('External Signing Request');
1028
	$section->addClass('toggle-external collapse');
1029

    
1030
	$section->addInput(new Form_Select(
1031
		'csr_keytype',
1032
		'*Key type',
1033
		$pconfig['csr_keytype'],
1034
		array_combine($cert_keytypes, $cert_keytypes)
1035
	));
1036

    
1037
	$group = new Form_Group($i == 0 ? '*Key length':'');
1038
	$group->addClass('csr_rsakeys');
1039
	$group->add(new Form_Select(
1040
		'csr_keylen',
1041
		null,
1042
		$pconfig['csr_keylen'],
1043
		array_combine($cert_keylens, $cert_keylens)
1044
	))->setHelp('The length to use when generating a new RSA key, in bits. %1$s' .
1045
		'The Key Length should not be lower than 2048 or some platforms ' .
1046
		'may consider the certificate invalid.', '<br/>');
1047
	$section->add($group);
1048

    
1049
	$group = new Form_Group($i == 0 ? '*Elliptic Curve Name':'');
1050
	$group->addClass('csr_ecnames');
1051
	$group->add(new Form_Select(
1052
		'csr_ecname',
1053
		null,
1054
		$pconfig['csr_ecname'],
1055
		$openssl_ecnames
1056
	));
1057
	$section->add($group);
1058

    
1059
	$section->addInput(new Form_Select(
1060
		'csr_digest_alg',
1061
		'*Digest Algorithm',
1062
		$pconfig['csr_digest_alg'],
1063
		array_combine($openssl_digest_algs, $openssl_digest_algs)
1064
	))->setHelp('The digest method used when the certificate is signed. %1$s' .
1065
		'The best practice is to use an algorithm stronger than SHA1. '.
1066
		'Some platforms may consider weaker digest algorithms invalid', '<br/>');
1067

    
1068
	$section->addInput(new Form_Input(
1069
		'csr_dn_commonname',
1070
		'*Common Name',
1071
		'text',
1072
		$pconfig['csr_dn_commonname'],
1073
		['placeholder' => 'e.g. internal-ca']
1074
	));
1075

    
1076
	$section->addInput(new Form_StaticText(
1077
		null,
1078
		gettext('The following certificate subject components are optional and may be left blank.')
1079
	));
1080

    
1081
	$section->addInput(new Form_Select(
1082
		'csr_dn_country',
1083
		'Country Code',
1084
		$pconfig['csr_dn_country'],
1085
		get_cert_country_codes()
1086
	));
1087

    
1088
	$section->addInput(new Form_Input(
1089
		'csr_dn_state',
1090
		'State or Province',
1091
		'text',
1092
		$pconfig['csr_dn_state'],
1093
		['placeholder' => 'e.g. Texas']
1094
	));
1095

    
1096
	$section->addInput(new Form_Input(
1097
		'csr_dn_city',
1098
		'City',
1099
		'text',
1100
		$pconfig['csr_dn_city'],
1101
		['placeholder' => 'e.g. Austin']
1102
	));
1103

    
1104
	$section->addInput(new Form_Input(
1105
		'csr_dn_organization',
1106
		'Organization',
1107
		'text',
1108
		$pconfig['csr_dn_organization'],
1109
		['placeholder' => 'e.g. My Company Inc']
1110
	));
1111

    
1112
	$section->addInput(new Form_Input(
1113
		'csr_dn_organizationalunit',
1114
		'Organizational Unit',
1115
		'text',
1116
		$pconfig['csr_dn_organizationalunit'],
1117
		['placeholder' => 'e.g. My Department Name (optional)']
1118
	));
1119

    
1120
	$form->add($section);
1121
	$section = new Form_Section('Choose an Existing Certificate');
1122
	$section->addClass('toggle-existing collapse');
1123

    
1124
	$existCerts = array();
1125

    
1126
	foreach ($config['cert'] as $cert) {
1127
		if (!is_array($cert) || empty($cert)) {
1128
			continue;
1129
		}
1130
		if (is_array($config['system']['user'][$userid]['cert'])) { // Could be MIA!
1131
			if (isset($userid) && in_array($cert['refid'], $config['system']['user'][$userid]['cert'])) {
1132
				continue;
1133
			}
1134
		}
1135

    
1136
		$ca = lookup_ca($cert['caref']);
1137
		if ($ca) {
1138
			$cert['descr'] .= " (CA: {$ca['descr']})";
1139
		}
1140

    
1141
		if (cert_in_use($cert['refid'])) {
1142
			$cert['descr'] .= " (In Use)";
1143
		}
1144
		if (is_cert_revoked($cert)) {
1145
			$cert['descr'] .= " (Revoked)";
1146
		}
1147

    
1148
		$existCerts[ $cert['refid'] ] = $cert['descr'];
1149
	}
1150

    
1151
	$section->addInput(new Form_Select(
1152
		'certref',
1153
		'*Existing Certificates',
1154
		$pconfig['certref'],
1155
		$existCerts
1156
	));
1157

    
1158
	$form->add($section);
1159

    
1160
	$section = new Form_Section('Certificate Attributes');
1161
	$section->addClass('toggle-external toggle-internal toggle-sign collapse');
1162

    
1163
	$section->addInput(new Form_StaticText(
1164
		gettext('Attribute Notes'),
1165
		'<span class="help-block">'.
1166
		gettext('The following attributes are added to certificates and ' .
1167
		'requests when they are created or signed. These attributes behave ' .
1168
		'differently depending on the selected mode.') .
1169
		'<br/><br/>' .
1170
		'<span class="toggle-internal collapse">' . gettext('For Internal Certificates, these attributes are added directly to the certificate as shown.') . '</span>' .
1171
		'<span class="toggle-external collapse">' .
1172
		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. ') .
1173
		'<br/><br/>' .
1174
		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>' .
1175
		'<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>' .
1176
		'</span>'
1177
	));
1178

    
1179
	$section->addInput(new Form_Select(
1180
		'type',
1181
		'*Certificate Type',
1182
		$pconfig['type'],
1183
		$cert_types
1184
	))->setHelp('Add type-specific usage attributes to the signed certificate.' .
1185
		' Used for placing usage restrictions on, or granting abilities to, ' .
1186
		'the signed certificate.');
1187

    
1188
	if (empty($pconfig['altnames']['item'])) {
1189
		$pconfig['altnames']['item'] = array(
1190
			array('type' => null, 'value' => null)
1191
		);
1192
	}
1193

    
1194
	$counter = 0;
1195
	$numrows = count($pconfig['altnames']['item']) - 1;
1196

    
1197
	foreach ($pconfig['altnames']['item'] as $item) {
1198

    
1199
		$group = new Form_Group($counter == 0 ? 'Alternative Names':'');
1200

    
1201
		$group->add(new Form_Select(
1202
			'altname_type' . $counter,
1203
			'Type',
1204
			$item['type'],
1205
			$cert_altname_types
1206
		))->setHelp(($counter == $numrows) ? 'Type':null);
1207

    
1208
		$group->add(new Form_Input(
1209
			'altname_value' . $counter,
1210
			null,
1211
			'text',
1212
			$item['value']
1213
		))->setHelp(($counter == $numrows) ? 'Value':null);
1214

    
1215
		$group->add(new Form_Button(
1216
			'deleterow' . $counter,
1217
			'Delete',
1218
			null,
1219
			'fa-trash'
1220
		))->addClass('btn-warning');
1221

    
1222
		$group->addClass('repeatable');
1223

    
1224
		$group->setHelp('Enter additional identifiers for the certificate ' .
1225
			'in this list. The Common Name field is automatically ' .
1226
			'added to the certificate as an Alternative Name. ' .
1227
			'The signing CA may ignore or change these values.');
1228

    
1229
		$section->add($group);
1230

    
1231
		$counter++;
1232
	}
1233

    
1234
	$section->addInput(new Form_Button(
1235
		'addrow',
1236
		'Add',
1237
		null,
1238
		'fa-plus'
1239
	))->addClass('btn-success');
1240

    
1241
	$form->add($section);
1242

    
1243
	if (($act == 'edit') && !empty($pconfig['key'])) {
1244
		$form->addGlobal(new Form_Button(
1245
			'exportpkey',
1246
			'Export Private Key',
1247
			null,
1248
			'fa-key'
1249
		))->addClass('btn-primary');
1250
		$form->addGlobal(new Form_Button(
1251
			'exportp12',
1252
			'Export PKCS#12',
1253
			null,
1254
			'fa-archive'
1255
		))->addClass('btn-primary');
1256
	}
1257

    
1258
	print $form;
1259

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

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

    
1266
	$section->addInput(new Form_Input(
1267
		'descr',
1268
		'*Descriptive name',
1269
		'text',
1270
		$pconfig['descr']
1271
	));
1272

    
1273
	$section->addInput(new Form_Textarea(
1274
		'csr',
1275
		'Signing request data',
1276
		$pconfig['csr']
1277
	))->setReadonly()
1278
	  ->setWidth(7)
1279
	  ->setHelp('Copy the certificate signing data from here and forward it to a certificate authority for signing.');
1280

    
1281
	$section->addInput(new Form_Textarea(
1282
		'cert',
1283
		'*Final certificate data',
1284
		$pconfig['cert']
1285
	))->setWidth(7)
1286
	  ->setHelp('Paste the certificate received from the certificate authority here.');
1287

    
1288
	if (isset($id) && $thiscert) {
1289
		$form->addGlobal(new Form_Input(
1290
			'id',
1291
			null,
1292
			'hidden',
1293
			$id
1294
		));
1295

    
1296
		$form->addGlobal(new Form_Input(
1297
			'act',
1298
			null,
1299
			'hidden',
1300
			'csr'
1301
		));
1302
	}
1303

    
1304
	$form->add($section);
1305

    
1306
	$form->addGlobal(new Form_Button(
1307
		'save',
1308
		'Update',
1309
		null,
1310
		'fa-save'
1311
	))->addClass('btn-primary');
1312

    
1313
	print($form);
1314
} else {
1315
?>
1316
<div class="panel panel-default" id="search-panel">
1317
	<div class="panel-heading">
1318
		<h2 class="panel-title">
1319
			<?=gettext('Search')?>
1320
			<span class="widget-heading-icon pull-right">
1321
				<a data-toggle="collapse" href="#search-panel_panel-body">
1322
					<i class="fa fa-plus-circle"></i>
1323
				</a>
1324
			</span>
1325
		</h2>
1326
	</div>
1327
	<div id="search-panel_panel-body" class="panel-body collapse in">
1328
		<div class="form-group">
1329
			<label class="col-sm-2 control-label">
1330
				<?=gettext("Search term")?>
1331
			</label>
1332
			<div class="col-sm-5"><input class="form-control" name="searchstr" id="searchstr" type="text"/></div>
1333
			<div class="col-sm-2">
1334
				<select id="where" class="form-control">
1335
					<option value="0"><?=gettext("Name")?></option>
1336
					<option value="1"><?=gettext("Distinguished Name")?></option>
1337
					<option value="2" selected><?=gettext("Both")?></option>
1338
				</select>
1339
			</div>
1340
			<div class="col-sm-3">
1341
				<a id="btnsearch" title="<?=gettext("Search")?>" class="btn btn-primary btn-sm"><i class="fa fa-search icon-embed-btn"></i><?=gettext("Search")?></a>
1342
				<a id="btnclear" title="<?=gettext("Clear")?>" class="btn btn-info btn-sm"><i class="fa fa-undo icon-embed-btn"></i><?=gettext("Clear")?></a>
1343
			</div>
1344
			<div class="col-sm-10 col-sm-offset-2">
1345
				<span class="help-block"><?=gettext('Enter a search string or *nix regular expression to search certificate names and distinguished names.')?></span>
1346
			</div>
1347
		</div>
1348
	</div>
1349
</div>
1350
<div class="panel panel-default">
1351
	<div class="panel-heading"><h2 class="panel-title"><?=gettext('Certificates')?></h2></div>
1352
	<div class="panel-body">
1353
		<div class="table-responsive">
1354
		<table class="table table-striped table-hover sortable-theme-bootstrap" data-sortable>
1355
			<thead>
1356
				<tr>
1357
					<th><?=gettext("Name")?></th>
1358
					<th><?=gettext("Issuer")?></th>
1359
					<th><?=gettext("Distinguished Name")?></th>
1360
					<th><?=gettext("In Use")?></th>
1361

    
1362
					<th class="col-sm-2"><?=gettext("Actions")?></th>
1363
				</tr>
1364
			</thead>
1365
			<tbody>
1366
<?php
1367

    
1368
$pluginparams = array();
1369
$pluginparams['type'] = 'certificates';
1370
$pluginparams['event'] = 'used_certificates';
1371
$certificates_used_by_packages = pkg_call_plugins('plugin_certificates', $pluginparams);
1372
foreach ($a_cert as $cert):
1373
	if (!is_array($cert) || empty($cert)) {
1374
		continue;
1375
	}
1376
	$name = htmlspecialchars($cert['descr']);
1377
	if ($cert['crt']) {
1378
		$subj = cert_get_subject($cert['crt']);
1379
		$issuer = cert_get_issuer($cert['crt']);
1380
		$purpose = cert_get_purpose($cert['crt']);
1381

    
1382
		if ($subj == $issuer) {
1383
			$caname = '<i>'. gettext("self-signed") .'</i>';
1384
		} else {
1385
			$caname = '<i>'. gettext("external").'</i>';
1386
		}
1387

    
1388
		$subj = htmlspecialchars(cert_escape_x509_chars($subj, true));
1389
	} else {
1390
		$subj = "";
1391
		$issuer = "";
1392
		$purpose = "";
1393
		$startdate = "";
1394
		$enddate = "";
1395
		$caname = "<em>" . gettext("private key only") . "</em>";
1396
	}
1397

    
1398
	if ($cert['csr']) {
1399
		$subj = htmlspecialchars(cert_escape_x509_chars(csr_get_subject($cert['csr']), true));
1400
		$caname = "<em>" . gettext("external - signature pending") . "</em>";
1401
	}
1402

    
1403
	$ca = lookup_ca($cert['caref']);
1404
	if ($ca) {
1405
		$caname = $ca['descr'];
1406
	}
1407
?>
1408
				<tr>
1409
					<td>
1410
						<?=$name?><br />
1411
						<?php if ($cert['type']): ?>
1412
							<i><?=$cert_types[$cert['type']]?></i><br />
1413
						<?php endif?>
1414
						<?php if (is_array($purpose)): ?>
1415
							CA: <b><?=$purpose['ca']?></b><br/>
1416
							<?=gettext("Server")?>: <b><?=$purpose['server']?></b><br/>
1417
						<?php endif?>
1418
					</td>
1419
					<td><?=$caname?></td>
1420
					<td>
1421
						<?=$subj?>
1422
						<?= cert_print_infoblock($cert); ?>
1423
						<?php cert_print_dates($cert);?>
1424
					</td>
1425
					<td>
1426
						<?php if (is_cert_revoked($cert)): ?>
1427
							<i><?=gettext("Revoked")?></i>
1428
						<?php endif?>
1429
						<?php if (is_webgui_cert($cert['refid'])): ?>
1430
							<?=gettext("webConfigurator")?>
1431
						<?php endif?>
1432
						<?php if (is_user_cert($cert['refid'])): ?>
1433
							<?=gettext("User Cert")?>
1434
						<?php endif?>
1435
						<?php if (is_openvpn_server_cert($cert['refid'])): ?>
1436
							<?=gettext("OpenVPN Server")?>
1437
						<?php endif?>
1438
						<?php if (is_openvpn_client_cert($cert['refid'])): ?>
1439
							<?=gettext("OpenVPN Client")?>
1440
						<?php endif?>
1441
						<?php if (is_ipsec_cert($cert['refid'])): ?>
1442
							<?=gettext("IPsec Tunnel")?>
1443
						<?php endif?>
1444
						<?php if (is_captiveportal_cert($cert['refid'])): ?>
1445
							<?=gettext("Captive Portal")?>
1446
						<?php endif?>
1447
						<?php if (is_unbound_cert($cert['refid'])): ?>
1448
							<?=gettext("DNS Resolver")?>
1449
						<?php endif?>
1450
						<?php echo cert_usedby_description($cert['refid'], $certificates_used_by_packages); ?>
1451
					</td>
1452
					<td>
1453
						<?php if (!$cert['csr']): ?>
1454
							<a href="system_certmanager.php?act=edit&amp;id=<?=$cert['refid']?>" class="fa fa-pencil" title="<?=gettext("Edit Certificate")?>"></a>
1455
							<a href="system_certmanager.php?act=exp&amp;id=<?=$cert['refid']?>" class="fa fa-certificate" title="<?=gettext("Export Certificate")?>"></a>
1456
							<?php if ($cert['prv']): ?>
1457
								<a href="system_certmanager.php?act=key&amp;id=<?=$cert['refid']?>" class="fa fa-key" title="<?=gettext("Export Key")?>"></a>
1458
								<a href="system_certmanager.php?act=p12&amp;id=<?=$cert['refid']?>" class="fa fa-archive" title="<?=gettext("Export P12")?>"></a>
1459
							<?php endif?>
1460
							<?php if (is_cert_locally_renewable($cert)): ?>
1461
								<a href="system_certmanager_renew.php?type=cert&amp;refid=<?=$cert['refid']?>" class="fa fa-repeat" title="<?=gettext("Reissue/Renew")?>"></a>
1462
							<?php endif ?>
1463
						<?php else: ?>
1464
							<a href="system_certmanager.php?act=csr&amp;id=<?=$cert['refid']?>" class="fa fa-pencil" title="<?=gettext("Update CSR")?>"></a>
1465
							<a href="system_certmanager.php?act=req&amp;id=<?=$cert['refid']?>" class="fa fa-sign-in" title="<?=gettext("Export Request")?>"></a>
1466
							<a href="system_certmanager.php?act=key&amp;id=<?=$cert['refid']?>" class="fa fa-key" title="<?=gettext("Export Key")?>"></a>
1467
						<?php endif?>
1468
						<?php if (!cert_in_use($cert['refid'])): ?>
1469
							<a href="system_certmanager.php?act=del&amp;id=<?=$cert['refid']?>" class="fa fa-trash" title="<?=gettext("Delete Certificate")?>" usepost></a>
1470
						<?php endif?>
1471
					</td>
1472
				</tr>
1473
<?php
1474
	endforeach; ?>
1475
			</tbody>
1476
		</table>
1477
		</div>
1478
	</div>
1479
</div>
1480

    
1481
<nav class="action-buttons">
1482
	<a href="?act=new" class="btn btn-success btn-sm">
1483
		<i class="fa fa-plus icon-embed-btn"></i>
1484
		<?=gettext("Add/Sign")?>
1485
	</a>
1486
</nav>
1487
<script type="text/javascript">
1488
//<![CDATA[
1489

    
1490
events.push(function() {
1491

    
1492
	// Make these controls plain buttons
1493
	$("#btnsearch").prop('type', 'button');
1494
	$("#btnclear").prop('type', 'button');
1495

    
1496
	// Search for a term in the entry name and/or dn
1497
	$("#btnsearch").click(function() {
1498
		var searchstr = $('#searchstr').val().toLowerCase();
1499
		var table = $("table tbody");
1500
		var where = $('#where').val();
1501

    
1502
		table.find('tr').each(function (i) {
1503
			var $tds = $(this).find('td'),
1504
				shortname = $tds.eq(0).text().trim().toLowerCase(),
1505
				dn = $tds.eq(2).text().trim().toLowerCase();
1506

    
1507
			regexp = new RegExp(searchstr);
1508
			if (searchstr.length > 0) {
1509
				if (!(regexp.test(shortname) && (where != 1)) && !(regexp.test(dn) && (where != 0))) {
1510
					$(this).hide();
1511
				} else {
1512
					$(this).show();
1513
				}
1514
			} else {
1515
				$(this).show();	// A blank search string shows all
1516
			}
1517
		});
1518
	});
1519

    
1520
	// Clear the search term and unhide all rows (that were hidden during a previous search)
1521
	$("#btnclear").click(function() {
1522
		var table = $("table tbody");
1523

    
1524
		$('#searchstr').val("");
1525

    
1526
		table.find('tr').each(function (i) {
1527
			$(this).show();
1528
		});
1529
	});
1530

    
1531
	// Hitting the enter key will do the same as clicking the search button
1532
	$("#searchstr").on("keyup", function (event) {
1533
		if (event.keyCode == 13) {
1534
			$("#btnsearch").get(0).click();
1535
		}
1536
	});
1537
});
1538
//]]>
1539
</script>
1540
<?php
1541
	include("foot.inc");
1542
	exit;
1543
}
1544

    
1545

    
1546
?>
1547
<script type="text/javascript">
1548
//<![CDATA[
1549
events.push(function() {
1550

    
1551
	$('.import_type_toggle').click(function() {
1552
		var x509 = (this.value === 'x509');
1553
		hideInput('cert', !x509);
1554
		setRequired('cert', x509);
1555
		hideInput('key', !x509);
1556
		setRequired('key', x509);
1557
		hideInput('pkcs12_cert', x509);
1558
		setRequired('pkcs12_cert', !x509);
1559
		hideInput('pkcs12_pass', x509);
1560
		hideCheckbox('pkcs12_intermediate', x509);
1561
	});
1562
	if ($('input[name=import_type]:checked').val() == 'x509') {
1563
		hideInput('pkcs12_cert', true);
1564
		setRequired('pkcs12_cert', false);
1565
		hideInput('pkcs12_pass', true);
1566
		hideCheckbox('pkcs12_intermediate', true);
1567
		hideInput('cert', false);
1568
		setRequired('cert', true);
1569
		hideInput('key', false);
1570
		setRequired('key', true);
1571
	} else if ($('input[name=import_type]:checked').val() == 'pkcs12') {
1572
		hideInput('cert', true);
1573
		setRequired('cert', false);
1574
		hideInput('key', true);
1575
		setRequired('key', false);
1576
		setRequired('pkcs12_cert', false);
1577
	}
1578

    
1579
<?php if ($internal_ca_count): ?>
1580
	function internalca_change() {
1581

    
1582
		caref = $('#caref').val();
1583

    
1584
		switch (caref) {
1585
<?php
1586
			foreach ($a_ca as $ca):
1587
				if (!$ca['prv']) {
1588
					continue;
1589
				}
1590

    
1591
				$subject = @cert_get_subject_hash($ca['crt']);
1592
				if (!is_array($subject) || empty($subject)) {
1593
					continue;
1594
				}
1595
?>
1596
				case "<?=$ca['refid'];?>":
1597
					$('#dn_country').val(<?=json_encode(cert_escape_x509_chars($subject['C'], true));?>);
1598
					$('#dn_state').val(<?=json_encode(cert_escape_x509_chars($subject['ST'], true));?>);
1599
					$('#dn_city').val(<?=json_encode(cert_escape_x509_chars($subject['L'], true));?>);
1600
					$('#dn_organization').val(<?=json_encode(cert_escape_x509_chars($subject['O'], true));?>);
1601
					$('#dn_organizationalunit').val(<?=json_encode(cert_escape_x509_chars($subject['OU'], true));?>);
1602
					break;
1603
<?php
1604
			endforeach;
1605
?>
1606
		}
1607
	}
1608

    
1609
	function set_csr_ro() {
1610
		var newcsr = ($('#csrtosign').val() == "new");
1611

    
1612
		$('#csrpaste').attr('readonly', !newcsr);
1613
		$('#keypaste').attr('readonly', !newcsr);
1614
		setRequired('csrpaste', newcsr);
1615
	}
1616

    
1617
	function check_lifetime() {
1618
		var maxserverlife = <?= $cert_strict_values['max_server_cert_lifetime'] ?>;
1619
		var ltid = '#lifetime';
1620
		if ($('#method').val() == "sign") {
1621
			ltid = '#csrsign_lifetime';
1622
		}
1623
		if (($('#type').val() == "server") && (parseInt($(ltid).val()) > maxserverlife)) {
1624
			$(ltid).parent().parent().removeClass("text-normal").addClass("text-warning");
1625
			$(ltid).removeClass("text-normal").addClass("text-warning");
1626
		} else {
1627
			$(ltid).parent().parent().removeClass("text-warning").addClass("text-normal");
1628
			$(ltid).removeClass("text-warning").addClass("text-normal");
1629
		}
1630
	}
1631
	function check_keylen() {
1632
		var min_keylen = <?= $cert_strict_values['min_private_key_bits'] ?>;
1633
		var klid = '#keylen';
1634
		if ($('#method').val() == "external") {
1635
			klid = '#csr_keylen';
1636
		}
1637
		/* Color the Parent/Label */
1638
		if (parseInt($(klid).val()) < min_keylen) {
1639
			$(klid).parent().parent().removeClass("text-normal").addClass("text-warning");
1640
		} else {
1641
			$(klid).parent().parent().removeClass("text-warning").addClass("text-normal");
1642
		}
1643
		/* Color individual options */
1644
		$(klid + " option").filter(function() {
1645
			return parseInt($(this).val()) < min_keylen;
1646
		}).removeClass("text-normal").addClass("text-warning").siblings().removeClass("text-warning").addClass("text-normal");
1647
	}
1648

    
1649
	function check_digest() {
1650
		var weak_algs = <?= json_encode($cert_strict_values['digest_blacklist']) ?>;
1651
		var daid = '#digest_alg';
1652
		if ($('#method').val() == "external") {
1653
			daid = '#csr_digest_alg';
1654
		} else if ($('#method').val() == "sign") {
1655
			daid = '#csrsign_digest_alg';
1656
		}
1657
		/* Color the Parent/Label */
1658
		if (jQuery.inArray($(daid).val(), weak_algs) > -1) {
1659
			$(daid).parent().parent().removeClass("text-normal").addClass("text-warning");
1660
		} else {
1661
			$(daid).parent().parent().removeClass("text-warning").addClass("text-normal");
1662
		}
1663
		/* Color individual options */
1664
		$(daid + " option").filter(function() {
1665
			return (jQuery.inArray($(this).val(), weak_algs) > -1);
1666
		}).removeClass("text-normal").addClass("text-warning").siblings().removeClass("text-warning").addClass("text-normal");
1667
	}
1668

    
1669
	// ---------- Click checkbox handlers ---------------------------------------------------------
1670

    
1671
	$('#type').on('change', function() {
1672
		check_lifetime();
1673
	});
1674
	$('#method').on('change', function() {
1675
		check_lifetime();
1676
		check_keylen();
1677
		check_digest();
1678
	});
1679
	$('#lifetime').on('change', function() {
1680
		check_lifetime();
1681
	});
1682
	$('#csrsign_lifetime').on('change', function() {
1683
		check_lifetime();
1684
	});
1685

    
1686
	$('#keylen').on('change', function() {
1687
		check_keylen();
1688
	});
1689
	$('#csr_keylen').on('change', function() {
1690
		check_keylen();
1691
	});
1692

    
1693
	$('#digest_alg').on('change', function() {
1694
		check_digest();
1695
	});
1696
	$('#csr_digest_alg').on('change', function() {
1697
		check_digest();
1698
	});
1699

    
1700
	$('#caref').on('change', function() {
1701
		internalca_change();
1702
	});
1703

    
1704
	$('#csrtosign').change(function () {
1705
		set_csr_ro();
1706
	});
1707

    
1708
	function change_keytype() {
1709
		hideClass('rsakeys', ($('#keytype').val() != 'RSA'));
1710
		hideClass('ecnames', ($('#keytype').val() != 'ECDSA'));
1711
	}
1712

    
1713
	$('#keytype').change(function () {
1714
		change_keytype();
1715
	});
1716

    
1717
	function change_csrkeytype() {
1718
		hideClass('csr_rsakeys', ($('#csr_keytype').val() != 'RSA'));
1719
		hideClass('csr_ecnames', ($('#csr_keytype').val() != 'ECDSA'));
1720
	}
1721

    
1722
	$('#csr_keytype').change(function () {
1723
		change_csrkeytype();
1724
	});
1725

    
1726
	// ---------- On initial page load ------------------------------------------------------------
1727

    
1728
	internalca_change();
1729
	set_csr_ro();
1730
	change_keytype();
1731
	change_csrkeytype();
1732
	check_lifetime();
1733
	check_keylen();
1734
	check_digest();
1735

    
1736
	// Suppress "Delete row" button if there are fewer than two rows
1737
	checkLastRow();
1738

    
1739

    
1740
<?php endif; ?>
1741

    
1742

    
1743
});
1744
//]]>
1745
</script>
1746
<?php
1747
include('foot.inc');
(193-193/228)