Project

General

Profile

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

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

    
30
require_once("guiconfig.inc");
31
require_once("certs.inc");
32
require_once("pfsense-utils.inc");
33

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

    
41
$cert_keylens = array("512", "1024", "2048", "3072", "4096", "7680", "8192", "15360", "16384");
42
$cert_types = array(
43
	"server" => "Server Certificate",
44
	"user" => "User Certificate");
45

    
46
$altname_types = array("DNS", "IP", "email", "URI");
47
global $openssl_digest_algs;
48

    
49
if (isset($_REQUEST['userid']) && is_numericint($_REQUEST['userid'])) {
50
	$userid = $_REQUEST['userid'];
51
}
52

    
53
if (isset($userid)) {
54
	$cert_methods["existing"] = gettext("Choose an existing certificate");
55
	if (!is_array($config['system']['user'])) {
56
		$config['system']['user'] = array();
57
	}
58
	$a_user =& $config['system']['user'];
59
}
60

    
61
if (isset($_REQUEST['id']) && is_numericint($_REQUEST['id'])) {
62
	$id = $_REQUEST['id'];
63
}
64

    
65
if (!is_array($config['ca'])) {
66
	$config['ca'] = array();
67
}
68

    
69
$a_ca =& $config['ca'];
70

    
71
if (!is_array($config['cert'])) {
72
	$config['cert'] = array();
73
}
74

    
75
$a_cert =& $config['cert'];
76

    
77
$internal_ca_count = 0;
78
foreach ($a_ca as $ca) {
79
	if ($ca['prv']) {
80
		$internal_ca_count++;
81
	}
82
}
83

    
84
$act = $_REQUEST['act'];
85

    
86
if ($_POST['act'] == "del") {
87

    
88
	if (!isset($a_cert[$id])) {
89
		pfSenseHeader("system_certmanager.php");
90
		exit;
91
	}
92

    
93
	unset($a_cert[$id]);
94
	write_config();
95
	$savemsg = sprintf(gettext("Certificate %s successfully deleted."), htmlspecialchars($a_cert[$id]['descr']));
96
	pfSenseHeader("system_certmanager.php");
97
	exit;
98
}
99

    
100
if ($act == "new") {
101
	$pconfig['method'] = $_POST['method'];
102
	$pconfig['keylen'] = "2048";
103
	$pconfig['digest_alg'] = "sha256";
104
	$pconfig['csr_keylen'] = "2048";
105
	$pconfig['csr_digest_alg'] = "sha256";
106
	$pconfig['type'] = "user";
107
	$pconfig['lifetime'] = "3650";
108
}
109

    
110
if ($act == "exp") {
111

    
112
	if (!$a_cert[$id]) {
113
		pfSenseHeader("system_certmanager.php");
114
		exit;
115
	}
116

    
117
	$exp_name = urlencode("{$a_cert[$id]['descr']}.crt");
118
	$exp_data = base64_decode($a_cert[$id]['crt']);
119
	$exp_size = strlen($exp_data);
120

    
121
	header("Content-Type: application/octet-stream");
122
	header("Content-Disposition: attachment; filename={$exp_name}");
123
	header("Content-Length: $exp_size");
124
	echo $exp_data;
125
	exit;
126
}
127

    
128
if ($act == "req") {
129

    
130
	if (!$a_cert[$id]) {
131
		pfSenseHeader("system_certmanager.php");
132
		exit;
133
	}
134

    
135
	$exp_name = urlencode("{$a_cert[$id]['descr']}.req");
136
	$exp_data = base64_decode($a_cert[$id]['csr']);
137
	$exp_size = strlen($exp_data);
138

    
139
	header("Content-Type: application/octet-stream");
140
	header("Content-Disposition: attachment; filename={$exp_name}");
141
	header("Content-Length: $exp_size");
142
	echo $exp_data;
143
	exit;
144
}
145

    
146
if ($act == "key") {
147

    
148
	if (!$a_cert[$id]) {
149
		pfSenseHeader("system_certmanager.php");
150
		exit;
151
	}
152

    
153
	$exp_name = urlencode("{$a_cert[$id]['descr']}.key");
154
	$exp_data = base64_decode($a_cert[$id]['prv']);
155
	$exp_size = strlen($exp_data);
156

    
157
	header("Content-Type: application/octet-stream");
158
	header("Content-Disposition: attachment; filename={$exp_name}");
159
	header("Content-Length: $exp_size");
160
	echo $exp_data;
161
	exit;
162
}
163

    
164
if ($act == "p12") {
165
	if (!$a_cert[$id]) {
166
		pfSenseHeader("system_certmanager.php");
167
		exit;
168
	}
169

    
170
	$exp_name = urlencode("{$a_cert[$id]['descr']}.p12");
171
	$args = array();
172
	$args['friendly_name'] = $a_cert[$id]['descr'];
173

    
174
	$ca = lookup_ca($a_cert[$id]['caref']);
175

    
176
	if ($ca) {
177
		$args['extracerts'] = openssl_x509_read(base64_decode($ca['crt']));
178
	}
179

    
180
	$res_crt = openssl_x509_read(base64_decode($a_cert[$id]['crt']));
181
	$res_key = openssl_pkey_get_private(array(0 => base64_decode($a_cert[$id]['prv']) , 1 => ""));
182

    
183
	$exp_data = "";
184
	openssl_pkcs12_export($res_crt, $exp_data, $res_key, null, $args);
185
	$exp_size = strlen($exp_data);
186

    
187
	header("Content-Type: application/octet-stream");
188
	header("Content-Disposition: attachment; filename={$exp_name}");
189
	header("Content-Length: $exp_size");
190
	echo $exp_data;
191
	exit;
192
}
193

    
194
if ($act == "csr") {
195
	if (!$a_cert[$id]) {
196
		pfSenseHeader("system_certmanager.php");
197
		exit;
198
	}
199

    
200
	$pconfig['descr'] = $a_cert[$id]['descr'];
201
	$pconfig['csr'] = base64_decode($a_cert[$id]['csr']);
202
}
203

    
204
if ($_POST['save']) {
205
	// This is just the blank alternate name that is added for display purposes. We don't want to validate/save it
206
	if ($_POST['altname_value0'] == "") {
207
		unset($_POST['altname_type0']);
208
		unset($_POST['altname_value0']);
209
	}
210

    
211
	if ($_POST['save'] == gettext("Save")) {
212
		$input_errors = array();
213
		$pconfig = $_POST;
214

    
215
		/* input validation */
216
		if ($pconfig['method'] == "sign") {
217
			$reqdfields = explode(" ",
218
				"descr catosignwith");
219
			$reqdfieldsn = array(
220
				gettext("Descriptive name"),
221
				gettext("CA to sign with"));
222

    
223
			if (($_POST['csrtosign'] === "new") && (!strstr($_POST['csrpaste'], "BEGIN CERTIFICATE REQUEST") || !strstr($_POST['csrpaste'], "END CERTIFICATE REQUEST"))) {
224
				$input_errors[] = gettext("This signing request does not appear to be valid.");
225
			}
226

    
227
			if ( (($_POST['csrtosign'] === "new") && (strlen($_POST['keypaste']) > 0)) && (!strstr($_POST['keypaste'], "BEGIN PRIVATE KEY") || !strstr($_POST['keypaste'], "END PRIVATE KEY"))) {
228
				$input_errors[] = gettext("This private does not appear to be valid.");
229
				$input_errors[] = gettext("Key data field should be blank, or a valid x509 private key");
230
			}
231

    
232
		}
233

    
234
		if ($pconfig['method'] == "import") {
235
			$reqdfields = explode(" ",
236
				"descr cert key");
237
			$reqdfieldsn = array(
238
				gettext("Descriptive name"),
239
				gettext("Certificate data"),
240
				gettext("Key data"));
241
			if ($_POST['cert'] && (!strstr($_POST['cert'], "BEGIN CERTIFICATE") || !strstr($_POST['cert'], "END CERTIFICATE"))) {
242
				$input_errors[] = gettext("This certificate does not appear to be valid.");
243
			}
244

    
245
			if (cert_get_publickey($_POST['cert'], false) != cert_get_publickey($_POST['key'], false, 'prv')) {
246
				$input_errors[] = gettext("The submitted private key does not match the submitted certificate data.");
247
			}
248
		}
249

    
250
		if ($pconfig['method'] == "internal") {
251
			$reqdfields = explode(" ",
252
				"descr caref keylen type lifetime dn_country dn_state dn_city ".
253
				"dn_organization dn_email dn_commonname");
254
			$reqdfieldsn = array(
255
				gettext("Descriptive name"),
256
				gettext("Certificate authority"),
257
				gettext("Key length"),
258
				gettext("Certificate Type"),
259
				gettext("Lifetime"),
260
				gettext("Distinguished name Country Code"),
261
				gettext("Distinguished name State or Province"),
262
				gettext("Distinguished name City"),
263
				gettext("Distinguished name Organization"),
264
				gettext("Distinguished name Email Address"),
265
				gettext("Distinguished name Common Name"));
266
		}
267

    
268
		if ($pconfig['method'] == "external") {
269
			$reqdfields = explode(" ",
270
				"descr csr_keylen csr_dn_country csr_dn_state csr_dn_city ".
271
				"csr_dn_organization csr_dn_email csr_dn_commonname");
272
			$reqdfieldsn = array(
273
				gettext("Descriptive name"),
274
				gettext("Key length"),
275
				gettext("Distinguished name Country Code"),
276
				gettext("Distinguished name State or Province"),
277
				gettext("Distinguished name City"),
278
				gettext("Distinguished name Organization"),
279
				gettext("Distinguished name Email Address"),
280
				gettext("Distinguished name Common Name"));
281
		}
282

    
283
		if ($pconfig['method'] == "existing") {
284
			$reqdfields = array("certref");
285
			$reqdfieldsn = array(gettext("Existing Certificate Choice"));
286
		}
287

    
288
		$altnames = array();
289
		do_input_validation($_POST, $reqdfields, $reqdfieldsn, $input_errors);
290

    
291
		if ($pconfig['method'] != "import" && $pconfig['method'] != "existing") {
292
			/* subjectAltNames */
293
			foreach ($_POST as $key => $value) {
294
				$entry = '';
295
				if (!substr_compare('altname_type', $key, 0, 12)) {
296
					$entry = substr($key, 12);
297
					$field = 'type';
298
				} elseif (!substr_compare('altname_value', $key, 0, 13)) {
299
					$entry = substr($key, 13);
300
					$field = 'value';
301
				}
302

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

    
309
			$pconfig['altnames']['item'] = $altnames;
310

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

    
343
			/* Make sure we do not have invalid characters in the fields for the certificate */
344

    
345
			if (preg_match("/[\?\>\<\&\/\\\"\']/", $_POST['descr'])) {
346
				array_push($input_errors, "The field 'Descriptive Name' contains invalid characters.");
347
			}
348

    
349
			for ($i = 0; $i < count($reqdfields); $i++) {
350
				if (preg_match('/email/', $reqdfields[$i])) { /* dn_email or csr_dn_name */
351
					if (preg_match("/[\!\#\$\%\^\(\)\~\?\>\<\&\/\\\,\"\']/", $_POST[$reqdfields[$i]])) {
352
						array_push($input_errors, gettext("The field 'Distinguished name Email Address' contains invalid characters."));
353
					}
354
				}
355
			}
356

    
357
			if (($pconfig['method'] != "external") && isset($_POST["keylen"]) && !in_array($_POST["keylen"], $cert_keylens)) {
358
				array_push($input_errors, gettext("Please select a valid Key Length."));
359
			}
360
			if (($pconfig['method'] != "external") && !in_array($_POST["digest_alg"], $openssl_digest_algs)) {
361
				array_push($input_errors, gettext("Please select a valid Digest Algorithm."));
362
			}
363

    
364
			if (($pconfig['method'] == "external") && isset($_POST["csr_keylen"]) && !in_array($_POST["csr_keylen"], $cert_keylens)) {
365
				array_push($input_errors, gettext("Please select a valid Key Length."));
366
			}
367
			if (($pconfig['method'] == "external") && !in_array($_POST["csr_digest_alg"], $openssl_digest_algs)) {
368
				array_push($input_errors, gettext("Please select a valid Digest Algorithm."));
369
			}
370
		}
371

    
372
		/* save modifications */
373
		if (!$input_errors) {
374

    
375
			if ($pconfig['method'] == "existing") {
376
				$cert = lookup_cert($pconfig['certref']);
377
				if ($cert && $a_user) {
378
					$a_user[$userid]['cert'][] = $cert['refid'];
379
				}
380
			} else if ($pconfig['method'] == "sign") { // Sign a CSR
381
				$csrid = lookup_cert($pconfig['csrtosign']);
382
				$caid =  lookup_ca($pconfig['catosignwith']);
383

    
384
				// Read the CSR from $config, or if a new one, from the textarea
385
				if ($pconfig['csrtosign'] === "new") {
386
					$csr = $pconfig['csrpaste'];
387
				} else {
388
					$csr = base64_decode($csrid['csr']);
389
				}
390

    
391
				$old_err_level = error_reporting(0);
392

    
393
				// Gather the information required for signed cert
394
				$ca = base64_decode($caid['crt']);
395
				$key = base64_decode($caid['prv']);
396
				$duration = $pconfig['duration'];
397
				$caref = $pconfig['catosignwith'];
398
				$type = (cert_get_purpose($csrid)['server'] === "Yes") ? "server":"user";
399

    
400
				// Sign the new cert and export it in x509 format
401
				openssl_x509_export(openssl_csr_sign($csr, $ca, $key, $duration, ['x509_extensions' => 'v3_req']), $n509);
402

    
403
				// Gather the details required to save the new cert
404
				$newcert = array();
405
				$newcert['refid'] = uniqid();
406
				$newcert['caref'] = $caref;
407
				$newcert['descr'] = $pconfig['descr'];
408
				$newcert['type'] = $type;
409
				$newcert['crt'] = base64_encode($n509);
410

    
411
				if ($pconfig['csrtosign'] === "new") {
412
					$newcert['prv'] = base64_encode($pconfig['keypaste']);
413
				} else {
414
					$newcert['prv'] = $csrid['prv'];
415
				}
416

    
417
				// Add it to the config file
418
				$config['cert'][] = $newcert;
419

    
420
				error_reporting($old_err_level);
421

    
422
			} else {
423
				$cert = array();
424
				$cert['refid'] = uniqid();
425
				if (isset($id) && $a_cert[$id]) {
426
					$cert = $a_cert[$id];
427
				}
428

    
429
				$cert['descr'] = $pconfig['descr'];
430

    
431
				$old_err_level = error_reporting(0); /* otherwise openssl_ functions throw warnings directly to a page screwing menu tab */
432

    
433
				if ($pconfig['method'] == "import") {
434
					cert_import($cert, $pconfig['cert'], $pconfig['key']);
435
				}
436

    
437
				if ($pconfig['method'] == "internal") {
438
					$dn = array(
439
						'countryName' => $pconfig['dn_country'],
440
						'stateOrProvinceName' => cert_escape_x509_chars($pconfig['dn_state']),
441
						'localityName' => cert_escape_x509_chars($pconfig['dn_city']),
442
						'organizationName' => cert_escape_x509_chars($pconfig['dn_organization']),
443
						'emailAddress' => cert_escape_x509_chars($pconfig['dn_email']),
444
						'commonName' => cert_escape_x509_chars($pconfig['dn_commonname']));
445
					if (!empty($pconfig['dn_organizationalunit'])) {
446
						$dn['organizationalUnitName'] = cert_escape_x509_chars($pconfig['dn_organizationalunit']);
447
					}
448
					if (is_ipaddr($pconfig['dn_commonname'])) {
449
						$altnames_tmp = array("IP:{$pconfig['dn_commonname']}");
450
					} else {
451
						$altnames_tmp = array("DNS:{$pconfig['dn_commonname']}");
452
					}
453
					if (count($altnames)) {
454
						foreach ($altnames as $altname) {
455
							// The CN is added as a SAN automatically, do not add it again.
456
							if ($altname['value'] != $pconfig['dn_commonname']) {
457
								$altnames_tmp[] = "{$altname['type']}:{$altname['value']}";
458
							}
459
						}
460
					}
461
					if (!empty($altnames_tmp)) {
462
						$dn['subjectAltName'] = implode(",", $altnames_tmp);
463
					}
464

    
465
					if (!cert_create($cert, $pconfig['caref'], $pconfig['keylen'], $pconfig['lifetime'], $dn, $pconfig['type'], $pconfig['digest_alg'])) {
466
						$input_errors = array();
467
						while ($ssl_err = openssl_error_string()) {
468
							if (strpos($ssl_err, 'NCONF_get_string:no value') === false) {
469
								array_push($input_errors, "openssl library returns: " . $ssl_err);
470
							}
471
						}
472
					}
473
				}
474

    
475
				if ($pconfig['method'] == "external") {
476
					$dn = array(
477
						'countryName' => $pconfig['csr_dn_country'],
478
						'stateOrProvinceName' => cert_escape_x509_chars($pconfig['csr_dn_state']),
479
						'localityName' => cert_escape_x509_chars($pconfig['csr_dn_city']),
480
						'organizationName' => cert_escape_x509_chars($pconfig['csr_dn_organization']),
481
						'emailAddress' => cert_escape_x509_chars($pconfig['csr_dn_email']),
482
						'commonName' => cert_escape_x509_chars($pconfig['csr_dn_commonname']));
483
					if (!empty($pconfig['csr_dn_organizationalunit'])) {
484
						$dn['organizationalUnitName'] = cert_escape_x509_chars($pconfig['csr_dn_organizationalunit']);
485
					}
486
					if (count($altnames)) {
487
						$altnames_tmp = "";
488
						foreach ($altnames as $altname) {
489
							$altnames_tmp[] = "{$altname['type']}:{$altname['value']}";
490
						}
491
						$dn['subjectAltName'] = implode(",", $altnames_tmp);
492
					}
493

    
494
					if (!csr_generate($cert, $pconfig['csr_keylen'], $dn, $pconfig['csr_digest_alg'])) {
495
						$input_errors = array();
496
						while ($ssl_err = openssl_error_string()) {
497
							if (strpos($ssl_err, 'NCONF_get_string:no value') === false) {
498
								array_push($input_errors, "openssl library returns: " . $ssl_err);
499
							}
500
						}
501
					}
502
				}
503

    
504
				error_reporting($old_err_level);
505

    
506
				if (isset($id) && $a_cert[$id]) {
507
					$a_cert[$id] = $cert;
508
				} else {
509
					$a_cert[] = $cert;
510
				}
511

    
512
				if (isset($a_user) && isset($userid)) {
513
					$a_user[$userid]['cert'][] = $cert['refid'];
514
				}
515
			}
516

    
517
			if (!$input_errors) {
518
				write_config();
519
			}
520

    
521
			if ($userid && !$input_errors) {
522
				post_redirect("system_usermanager.php", array('act' => 'edit', 'userid' => $userid));
523
				exit;
524
			}
525
		}
526
	}
527

    
528
	if ($_POST['save'] == gettext("Update")) {
529
		unset($input_errors);
530
		$pconfig = $_POST;
531

    
532
		/* input validation */
533
		$reqdfields = explode(" ", "descr cert");
534
		$reqdfieldsn = array(
535
			gettext("Descriptive name"),
536
			gettext("Final Certificate data"));
537

    
538
		do_input_validation($_POST, $reqdfields, $reqdfieldsn, $input_errors);
539

    
540
		if (preg_match("/[\?\>\<\&\/\\\"\']/", $_POST['descr'])) {
541
			array_push($input_errors, "The field 'Descriptive Name' contains invalid characters.");
542
		}
543

    
544
//		old way
545
		/* make sure this csr and certificate subjects match */
546
//		$subj_csr = csr_get_subject($pconfig['csr'], false);
547
//		$subj_cert = cert_get_subject($pconfig['cert'], false);
548
//
549
//		if (!isset($_POST['ignoresubjectmismatch']) && !($_POST['ignoresubjectmismatch'] == "yes")) {
550
//			if (strcmp($subj_csr, $subj_cert)) {
551
//				$input_errors[] = sprintf(gettext("The certificate subject '%s' does not match the signing request subject."), $subj_cert);
552
//				$subject_mismatch = true;
553
//			}
554
//		}
555
		$mod_csr = cert_get_publickey($pconfig['csr'], false, 'csr');
556
		$mod_cert = cert_get_publickey($pconfig['cert'], false);
557

    
558
		if (strcmp($mod_csr, $mod_cert)) {
559
			// simply: if the moduli don't match, then the private key and public key won't match
560
			$input_errors[] = sprintf(gettext("The certificate public key does not match the signing request public key."), $subj_cert);
561
			$subject_mismatch = true;
562
		}
563

    
564
		/* save modifications */
565
		if (!$input_errors) {
566

    
567
			$cert = $a_cert[$id];
568

    
569
			$cert['descr'] = $pconfig['descr'];
570

    
571
			csr_complete($cert, $pconfig['cert']);
572

    
573
			$a_cert[$id] = $cert;
574

    
575
			write_config();
576

    
577
			pfSenseHeader("system_certmanager.php");
578
		}
579
	}
580
}
581

    
582
$pgtitle = array(gettext("System"), gettext("Certificate Manager"), gettext("Certificates"));
583
$pglinks = array("", "system_camanager.php", "system_certmanager.php");
584

    
585
if (($act == "new" || ($_POST['save'] == gettext("Save") && $input_errors)) || ($act == "csr" || ($_POST['save'] == gettext("Update") && $input_errors))) {
586
	$pgtitle[] = gettext('Edit');
587
	$pglinks[] = "@self";
588
}
589
include("head.inc");
590

    
591
if ($input_errors) {
592
	print_input_errors($input_errors);
593
}
594

    
595
if ($savemsg) {
596
	print_info_box($savemsg, 'success');
597
}
598

    
599
$tab_array = array();
600
$tab_array[] = array(gettext("CAs"), false, "system_camanager.php");
601
$tab_array[] = array(gettext("Certificates"), true, "system_certmanager.php");
602
$tab_array[] = array(gettext("Certificate Revocation"), false, "system_crlmanager.php");
603
display_top_tabs($tab_array);
604

    
605
// Load valid country codes
606
$dn_cc = array();
607
if (file_exists("/etc/ca_countries")) {
608
	$dn_cc_file=file("/etc/ca_countries");
609
	foreach ($dn_cc_file as $line) {
610
		if (preg_match('/^(\S*)\s(.*)$/', $line, $matches)) {
611
			$dn_cc[$matches[1]] = $matches[1];
612
		}
613
	}
614
}
615

    
616
if ($act == "new" || (($_POST['save'] == gettext("Save")) && $input_errors)) {
617
	$form = new Form();
618
	$form->setAction('system_certmanager.php?act=edit');
619

    
620
	if (isset($userid) && $a_user) {
621
		$form->addGlobal(new Form_Input(
622
			'userid',
623
			null,
624
			'hidden',
625
			$userid
626
		));
627
	}
628

    
629
	if (isset($id) && $a_cert[$id]) {
630
		$form->addGlobal(new Form_Input(
631
			'id',
632
			null,
633
			'hidden',
634
			$id
635
		));
636
	}
637

    
638
	$section = new Form_Section('Add/Sign a New Certificate');
639

    
640
	if (!isset($id)) {
641
		$section->addInput(new Form_Select(
642
			'method',
643
			'*Method',
644
			$pconfig['method'],
645
			$cert_methods
646
		))->toggles();
647
	}
648

    
649
	$section->addInput(new Form_Input(
650
		'descr',
651
		'*Descriptive name',
652
		'text',
653
		($a_user && empty($pconfig['descr'])) ? $a_user[$userid]['name'] : $pconfig['descr']
654
	))->addClass('toggle-existing');
655

    
656
	$form->add($section);
657

    
658
	// Return an array containing the IDs od all CAs
659
	function list_cas() {
660
		global $a_ca;
661
		$allCas = array();
662

    
663
		foreach ($a_ca as $ca) {
664
			if ($ca['prv']) {
665
				$allCas[$ca['refid']] = $ca['descr'];
666
			}
667
		}
668

    
669
		return $allCas;
670
	}
671

    
672
	// Return an array containing the IDs od all CSRs
673
	function list_csrs() {
674
		global $config;
675
		$allCsrs = array();
676

    
677
		foreach ($config['cert'] as $cert) {
678
			if ($cert['csr']) {
679
				$allCsrs[$cert['refid']] = $cert['descr'];
680
			}
681
		}
682

    
683
		return ['new' => gettext('New CSR (Paste below)')] + $allCsrs;
684
	}
685

    
686
	$section = new Form_Section('Sign CSR');
687
	$section->addClass('toggle-sign collapse');
688

    
689
	$section->AddInput(new Form_Select(
690
		'catosignwith',
691
		'*CA to sign with',
692
		$pconfig['catosignwith'],
693
		list_cas()
694
	));
695

    
696
	$section->AddInput(new Form_Select(
697
		'csrtosign',
698
		'*CSR to sign',
699
		isset($pconfig['csrtosign']) ? $pconfig['csrtosign'] : 'new',
700
		list_csrs()
701
	));
702

    
703
	$section->addInput(new Form_Input(
704
		'duration',
705
		'*Certificate duration (days)',
706
		'number',
707
		$pconfig['duration'] ? $pconfig['duration']:'3650'
708
	));
709

    
710
	$section->addInput(new Form_Textarea(
711
		'csrpaste',
712
		'CSR data',
713
		$pconfig['csrpaste']
714
	))->setHelp('Paste a Certificate Signing Request in X.509 PEM format here.');
715

    
716
	$section->addInput(new Form_Textarea(
717
		'keypaste',
718
		'Key data',
719
		$pconfig['keypaste']
720
	))->setHelp('Optionally paste a private key here. The key will be associated with the newly signed certificate in pfSense');
721

    
722
	$form->add($section);
723

    
724
	$section = new Form_Section('Import Certificate');
725
	$section->addClass('toggle-import collapse');
726

    
727
	$section->addInput(new Form_Textarea(
728
		'cert',
729
		'*Certificate data',
730
		$pconfig['cert']
731
	))->setHelp('Paste a certificate in X.509 PEM format here.');
732

    
733
	$section->addInput(new Form_Textarea(
734
		'key',
735
		'*Private key data',
736
		$pconfig['key']
737
	))->setHelp('Paste a private key in X.509 PEM format here.');
738

    
739
	$form->add($section);
740
	$section = new Form_Section('Internal Certificate');
741
	$section->addClass('toggle-internal collapse');
742

    
743
	if (!$internal_ca_count) {
744
		$section->addInput(new Form_StaticText(
745
			'*Certificate authority',
746
			gettext('No internal Certificate Authorities have been defined. ') .
747
			gettext('An internal CA must be defined in order to create an internal certificate. ') .
748
			sprintf(gettext('%1$sCreate%2$s an internal CA.'), '<a href="system_camanager.php?act=new&amp;method=internal"> ', '</a>')
749
		));
750
	} else {
751
		$allCas = array();
752
		foreach ($a_ca as $ca) {
753
			if (!$ca['prv']) {
754
				continue;
755
			}
756

    
757
			$allCas[ $ca['refid'] ] = $ca['descr'];
758
		}
759

    
760
		$section->addInput(new Form_Select(
761
			'caref',
762
			'*Certificate authority',
763
			$pconfig['caref'],
764
			$allCas
765
		));
766
	}
767

    
768
	$section->addInput(new Form_Select(
769
		'keylen',
770
		'*Key length',
771
		$pconfig['keylen'],
772
		array_combine($cert_keylens, $cert_keylens)
773
	));
774

    
775
	$section->addInput(new Form_Select(
776
		'digest_alg',
777
		'*Digest Algorithm',
778
		$pconfig['digest_alg'],
779
		array_combine($openssl_digest_algs, $openssl_digest_algs)
780
	))->setHelp('NOTE: It is recommended to use an algorithm stronger than '.
781
		'SHA1 when possible.');
782

    
783
	$section->addInput(new Form_Select(
784
		'type',
785
		'*Certificate Type',
786
		$pconfig['type'],
787
		$cert_types
788
	))->setHelp('Type of certificate to generate. Used for placing '.
789
		'restrictions on the usage of the generated certificate.');
790

    
791
	$section->addInput(new Form_Input(
792
		'lifetime',
793
		'*Lifetime (days)',
794
		'number',
795
		$pconfig['lifetime']
796
	));
797

    
798
	$section->addInput(new Form_Select(
799
		'dn_country',
800
		'*Country Code',
801
		$pconfig['dn_country'],
802
		$dn_cc
803
	));
804

    
805
	$section->addInput(new Form_Input(
806
		'dn_state',
807
		'*State or Province',
808
		'text',
809
		$pconfig['dn_state'],
810
		['placeholder' => 'e.g. Texas']
811
	));
812

    
813
	$section->addInput(new Form_Input(
814
		'dn_city',
815
		'*City',
816
		'text',
817
		$pconfig['dn_city'],
818
		['placeholder' => 'e.g. Austin']
819
	));
820

    
821
	$section->addInput(new Form_Input(
822
		'dn_organization',
823
		'*Organization',
824
		'text',
825
		$pconfig['dn_organization'],
826
		['placeholder' => 'e.g. My Company Inc']
827
	));
828

    
829
	$section->addInput(new Form_Input(
830
		'dn_organizationalunit',
831
		'Organizational Unit',
832
		'text',
833
		$pconfig['dn_organizationalunit'],
834
		['placeholder' => 'e.g. My Department Name (optional)']
835
	));
836

    
837
	$section->addInput(new Form_Input(
838
		'dn_email',
839
		'*Email Address',
840
		'text',
841
		$pconfig['dn_email'],
842
		['placeholder' => 'e.g. admin@mycompany.com']
843
	));
844

    
845
	$section->addInput(new Form_Input(
846
		'dn_commonname',
847
		'*Common Name',
848
		'text',
849
		$pconfig['dn_commonname'],
850
		['placeholder' => 'e.g. www.example.com']
851
	));
852

    
853
	if (empty($pconfig['altnames']['item'])) {
854
		$pconfig['altnames']['item'] = array(
855
			array('type' => null, 'value' => null)
856
		);
857
	}
858

    
859
	$counter = 0;
860
	$numrows = count($pconfig['altnames']['item']) - 1;
861

    
862
	foreach ($pconfig['altnames']['item'] as $item) {
863

    
864
		$group = new Form_Group($counter == 0 ? 'Alternative Names':'');
865

    
866
		$group->add(new Form_Select(
867
			'altname_type' . $counter,
868
			'Type',
869
			$item['type'],
870
			array(
871
				'DNS' => gettext('FQDN or Hostname'),
872
				'IP' => gettext('IP address'),
873
				'URI' => gettext('URI'),
874
				'email' => gettext('email address'),
875
			)
876
		))->setHelp(($counter == $numrows) ? 'Type':null);
877

    
878
		$group->add(new Form_Input(
879
			'altname_value' . $counter,
880
			null,
881
			'text',
882
			$item['value']
883
		))->setHelp(($counter == $numrows) ? 'Value':null);
884

    
885
		$group->add(new Form_Button(
886
			'deleterow' . $counter,
887
			'Delete',
888
			null,
889
			'fa-trash'
890
		))->addClass('btn-warning');
891

    
892
		$group->addClass('repeatable');
893

    
894
		$group->setHelp('Enter additional identifiers for the certificate in this list. The Common Name field is automatically added to the certificate as an Alternative Name.');
895

    
896
		$section->add($group);
897

    
898
		$counter++;
899
	}
900

    
901
	$section->addInput(new Form_Button(
902
		'addrow',
903
		'Add',
904
		null,
905
		'fa-plus'
906
	))->addClass('btn-success');
907

    
908

    
909
	$form->add($section);
910
	$section = new Form_Section('External Signing Request');
911
	$section->addClass('toggle-external collapse');
912

    
913
	$section->addInput(new Form_Select(
914
		'csr_keylen',
915
		'*Key length',
916
		$pconfig['csr_keylen'],
917
		array_combine($cert_keylens, $cert_keylens)
918
	));
919

    
920
	$section->addInput(new Form_Select(
921
		'csr_digest_alg',
922
		'*Digest Algorithm',
923
		$pconfig['csr_digest_alg'],
924
		array_combine($openssl_digest_algs, $openssl_digest_algs)
925
	))->setHelp('NOTE: It is recommended to use an algorithm stronger than '.
926
		'SHA1 when possible');
927

    
928
	$section->addInput(new Form_Select(
929
		'csr_dn_country',
930
		'*Country Code',
931
		$pconfig['csr_dn_country'],
932
		$dn_cc
933
	));
934

    
935
	$section->addInput(new Form_Input(
936
		'csr_dn_state',
937
		'*State or Province',
938
		'text',
939
		$pconfig['csr_dn_state'],
940
		['placeholder' => 'e.g. Texas']
941
	));
942

    
943
	$section->addInput(new Form_Input(
944
		'csr_dn_city',
945
		'*City',
946
		'text',
947
		$pconfig['csr_dn_city'],
948
		['placeholder' => 'e.g. Austin']
949
	));
950

    
951
	$section->addInput(new Form_Input(
952
		'csr_dn_organization',
953
		'*Organization',
954
		'text',
955
		$pconfig['csr_dn_organization'],
956
		['placeholder' => 'e.g. My Company Inc']
957
	));
958

    
959
	$section->addInput(new Form_Input(
960
		'csr_dn_organizationalunit',
961
		'Organizational Unit',
962
		'text',
963
		$pconfig['csr_dn_organizationalunit'],
964
		['placeholder' => 'e.g. My Department Name (optional)']
965
	));
966

    
967
	$section->addInput(new Form_Input(
968
		'csr_dn_email',
969
		'*Email Address',
970
		'text',
971
		$pconfig['csr_dn_email'],
972
		['placeholder' => 'e.g. admin@mycompany.com']
973
	));
974

    
975
	$section->addInput(new Form_Input(
976
		'csr_dn_commonname',
977
		'*Common Name',
978
		'text',
979
		$pconfig['csr_dn_commonname'],
980
		['placeholder' => 'e.g. internal-ca']
981
	));
982

    
983
	$form->add($section);
984
	$section = new Form_Section('Choose an Existing Certificate');
985
	$section->addClass('toggle-existing collapse');
986

    
987
	$existCerts = array();
988

    
989
	foreach ($config['cert'] as $cert)	{
990
		if (is_array($config['system']['user'][$userid]['cert'])) { // Could be MIA!
991
			if (isset($userid) && in_array($cert['refid'], $config['system']['user'][$userid]['cert'])) {
992
				continue;
993
			}
994
		}
995

    
996
		$ca = lookup_ca($cert['caref']);
997
		if ($ca) {
998
			$cert['descr'] .= " (CA: {$ca['descr']})";
999
		}
1000

    
1001
		if (cert_in_use($cert['refid'])) {
1002
			$cert['descr'] .= " (In Use)";
1003
		}
1004
		if (is_cert_revoked($cert)) {
1005
			$cert['descr'] .= " (Revoked)";
1006
		}
1007

    
1008
		$existCerts[ $cert['refid'] ] = $cert['descr'];
1009
	}
1010

    
1011
	$section->addInput(new Form_Select(
1012
		'certref',
1013
		'*Existing Certificates',
1014
		$pconfig['certref'],
1015
		$existCerts
1016
	));
1017

    
1018
	$form->add($section);
1019
	print $form;
1020

    
1021
} else if ($act == "csr" || (($_POST['save'] == gettext("Update")) && $input_errors)) {
1022
	$form = new Form(false);
1023
	$form->setAction('system_certmanager.php?act=csr');
1024

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

    
1027
	$section->addInput(new Form_Input(
1028
		'descr',
1029
		'*Descriptive name',
1030
		'text',
1031
		$pconfig['descr']
1032
	));
1033

    
1034
	$section->addInput(new Form_Textarea(
1035
		'csr',
1036
		'Signing request data',
1037
		$pconfig['csr']
1038
	))->setReadonly()
1039
	  ->setWidth(7)
1040
	  ->setHelp('Copy the certificate signing data from here and forward it to a certificate authority for signing.');
1041

    
1042
	$section->addInput(new Form_Textarea(
1043
		'cert',
1044
		'*Final certificate data',
1045
		$pconfig['cert']
1046
	))->setWidth(7)
1047
	  ->setHelp('Paste the certificate received from the certificate authority here.');
1048

    
1049
	 if (isset($id) && $a_cert[$id]) {
1050
		 $section->addInput(new Form_Input(
1051
			'id',
1052
			null,
1053
			'hidden',
1054
			$id
1055
		 ));
1056

    
1057
		 $section->addInput(new Form_Input(
1058
			'act',
1059
			null,
1060
			'hidden',
1061
			'csr'
1062
		 ));
1063
	 }
1064

    
1065
	$form->add($section);
1066

    
1067
	$form->addGlobal(new Form_Button(
1068
		'save',
1069
		'Update',
1070
		null,
1071
		'fa-save'
1072
	))->addClass('btn-primary');
1073

    
1074
	print($form);
1075
} else {
1076
?>
1077
<div class="panel panel-default">
1078
	<div class="panel-heading"><h2 class="panel-title"><?=gettext('Certificates')?></h2></div>
1079
	<div class="panel-body">
1080
		<div class="table-responsive">
1081
		<table class="table table-striped table-hover">
1082
			<thead>
1083
				<tr>
1084
					<th><?=gettext("Name")?></th>
1085
					<th><?=gettext("Issuer")?></th>
1086
					<th><?=gettext("Distinguished Name")?></th>
1087
					<th><?=gettext("In Use")?></th>
1088

    
1089
					<th class="col-sm-2"><?=gettext("Actions")?></th>
1090
				</tr>
1091
			</thead>
1092
			<tbody>
1093
<?php
1094

    
1095
$pluginparams = array();
1096
$pluginparams['type'] = 'certificates';
1097
$pluginparams['event'] = 'used_certificates';
1098
$certificates_used_by_packages = pkg_call_plugins('plugin_certificates', $pluginparams);
1099
$i = 0;
1100
foreach ($a_cert as $i => $cert):
1101
	$name = htmlspecialchars($cert['descr']);
1102

    
1103
	if ($cert['crt']) {
1104
		$subj = cert_get_subject($cert['crt']);
1105
		$issuer = cert_get_issuer($cert['crt']);
1106
		$purpose = cert_get_purpose($cert['crt']);
1107
		$sans = cert_get_sans($cert['crt']);
1108
		list($startdate, $enddate) = cert_get_dates($cert['crt']);
1109

    
1110
		if ($subj == $issuer) {
1111
			$caname = '<i>'. gettext("self-signed") .'</i>';
1112
		} else {
1113
			$caname = '<i>'. gettext("external").'</i>';
1114
		}
1115

    
1116
		$subj = htmlspecialchars(cert_escape_x509_chars($subj, true));
1117
	} else {
1118
		$subj = "";
1119
		$issuer = "";
1120
		$purpose = "";
1121
		$startdate = "";
1122
		$enddate = "";
1123
		$caname = "<em>" . gettext("private key only") . "</em>";
1124
	}
1125

    
1126
	if ($cert['csr']) {
1127
		$subj = htmlspecialchars(cert_escape_x509_chars(csr_get_subject($cert['csr']), true));
1128
		$sans = cert_get_sans($cert['crt']);
1129
		$caname = "<em>" . gettext("external - signature pending") . "</em>";
1130
	}
1131

    
1132
	$ca = lookup_ca($cert['caref']);
1133
	if ($ca) {
1134
		$caname = $ca['descr'];
1135
	}
1136
?>
1137
				<tr>
1138
					<td>
1139
						<?=$name?><br />
1140
						<?php if ($cert['type']): ?>
1141
							<i><?=$cert_types[$cert['type']]?></i><br />
1142
						<?php endif?>
1143
						<?php if (is_array($purpose)): ?>
1144
							CA: <b><?=$purpose['ca']?></b><br/>
1145
							<?=gettext("Server")?>: <b><?=$purpose['server']?></b><br/>
1146
						<?php endif?>
1147
					</td>
1148
					<td><?=$caname?></td>
1149
					<td>
1150
						<?=$subj?>
1151
						<?php
1152
						$certextinfo = "";
1153
						if (is_array($sans) && !empty($sans)) {
1154
							$certextinfo .= '<b>' . gettext("SAN: ") . '</b> ';
1155
							$certextinfo .= htmlspecialchars(implode(', ', $sans));
1156
							$certextinfo .= '<br/>';
1157
						}
1158
						if (is_array($purpose) && !empty($purpose['ku'])) {
1159
							$certextinfo .= '<b>' . gettext("KU: ") . '</b> ';
1160
							$certextinfo .= htmlspecialchars(implode(', ', $purpose['ku']));
1161
							$certextinfo .= '<br/>';
1162
						}
1163
						if (is_array($purpose) && !empty($purpose['eku'])) {
1164
							$certextinfo .= '<b>' . gettext("EKU: ") . '</b> ';
1165
							$certextinfo .= htmlspecialchars(implode(', ', $purpose['eku']));
1166
						}
1167
						?>
1168
						<?php if (!empty($certextinfo)): ?>
1169
							<div class="infoblock">
1170
							<? print_info_box($certextinfo, 'info', false); ?>
1171
							</div>
1172
						<?php endif?>
1173

    
1174
						<?php if (!empty($startdate) || !empty($enddate)): ?>
1175
						<br />
1176
						<small>
1177
							<?=gettext("Valid From")?>: <b><?=$startdate ?></b><br /><?=gettext("Valid Until")?>: <b><?=$enddate ?></b>
1178
						</small>
1179
						<?php endif?>
1180
					</td>
1181
					<td>
1182
						<?php if (is_cert_revoked($cert)): ?>
1183
							<i><?=gettext("Revoked")?></i>
1184
						<?php endif?>
1185
						<?php if (is_webgui_cert($cert['refid'])): ?>
1186
							<?=gettext("webConfigurator")?>
1187
						<?php endif?>
1188
						<?php if (is_user_cert($cert['refid'])): ?>
1189
							<?=gettext("User Cert")?>
1190
						<?php endif?>
1191
						<?php if (is_openvpn_server_cert($cert['refid'])): ?>
1192
							<?=gettext("OpenVPN Server")?>
1193
						<?php endif?>
1194
						<?php if (is_openvpn_client_cert($cert['refid'])): ?>
1195
							<?=gettext("OpenVPN Client")?>
1196
						<?php endif?>
1197
						<?php if (is_ipsec_cert($cert['refid'])): ?>
1198
							<?=gettext("IPsec Tunnel")?>
1199
						<?php endif?>
1200
						<?php if (is_captiveportal_cert($cert['refid'])): ?>
1201
							<?=gettext("Captive Portal")?>
1202
						<?php endif?>
1203
						<?php echo cert_usedby_description($cert['refid'], $certificates_used_by_packages); ?>
1204
					</td>
1205
					<td>
1206
						<?php if (!$cert['csr']): ?>
1207
							<a href="system_certmanager.php?act=exp&amp;id=<?=$i?>" class="fa fa-certificate" title="<?=gettext("Export Certificate")?>"></a>
1208
							<?php if ($cert['prv']): ?>
1209
								<a href="system_certmanager.php?act=key&amp;id=<?=$i?>" class="fa fa-key" title="<?=gettext("Export Key")?>"></a>
1210
							<?php endif?>
1211
							<a href="system_certmanager.php?act=p12&amp;id=<?=$i?>" class="fa fa-archive" title="<?=gettext("Export P12")?>"></a>
1212
						<?php else: ?>
1213
							<a href="system_certmanager.php?act=csr&amp;id=<?=$i?>" class="fa fa-pencil" title="<?=gettext("Update CSR")?>"></a>
1214
							<a href="system_certmanager.php?act=req&amp;id=<?=$i?>" class="fa fa-sign-in" title="<?=gettext("Export Request")?>"></a>
1215
							<a href="system_certmanager.php?act=key&amp;id=<?=$i?>" class="fa fa-key" title="<?=gettext("Export Key")?>"></a>
1216
						<?php endif?>
1217
						<?php if (!cert_in_use($cert['refid'])): ?>
1218
							<a href="system_certmanager.php?act=del&amp;id=<?=$i?>" class="fa fa-trash" title="<?=gettext("Delete Certificate")?>" usepost></a>
1219
						<?php endif?>
1220
					</td>
1221
				</tr>
1222
<?php
1223
	$i++;
1224
	endforeach; ?>
1225
			</tbody>
1226
		</table>
1227
		</div>
1228
	</div>
1229
</div>
1230

    
1231
<nav class="action-buttons">
1232
	<a href="?act=new" class="btn btn-success btn-sm">
1233
		<i class="fa fa-plus icon-embed-btn"></i>
1234
		<?=gettext("Add/Sign")?>
1235
	</a>
1236
</nav>
1237
<?php
1238
	include("foot.inc");
1239
	exit;
1240
}
1241

    
1242

    
1243
?>
1244
<script type="text/javascript">
1245
//<![CDATA[
1246
events.push(function() {
1247

    
1248
<?php if ($internal_ca_count): ?>
1249
	function internalca_change() {
1250

    
1251
		caref = $('#caref').val();
1252

    
1253
		switch (caref) {
1254
<?php
1255
			foreach ($a_ca as $ca):
1256
				if (!$ca['prv']) {
1257
					continue;
1258
				}
1259

    
1260
				$subject = cert_get_subject_array($ca['crt']);
1261
?>
1262
				case "<?=$ca['refid'];?>":
1263
					$('#dn_country').val(<?=json_encode(cert_escape_x509_chars($subject[0]['v'], true));?>);
1264
					$('#dn_state').val(<?=json_encode(cert_escape_x509_chars($subject[1]['v'], true));?>);
1265
					$('#dn_city').val(<?=json_encode(cert_escape_x509_chars($subject[2]['v'], true));?>);
1266
					$('#dn_organization').val(<?=json_encode(cert_escape_x509_chars($subject[3]['v'], true));?>);
1267
					$('#dn_email').val(<?=json_encode(cert_escape_x509_chars($subject[4]['v'], true));?>);
1268
					$('#dn_organizationalunit').val(<?=json_encode(cert_escape_x509_chars($subject[6]['v'], true));?>);
1269
					break;
1270
<?php
1271
			endforeach;
1272
?>
1273
		}
1274
	}
1275

    
1276
	function set_csr_ro() {
1277
		var newcsr = ($('#csrtosign').val() == "new");
1278

    
1279
		$('#csrpaste').attr('readonly', !newcsr);
1280
		$('#keypaste').attr('readonly', !newcsr);
1281
		setRequired('csrpaste', newcsr);
1282
	}
1283

    
1284
	// ---------- Click checkbox handlers ---------------------------------------------------------
1285

    
1286
	$('#caref').on('change', function() {
1287
		internalca_change();
1288
	});
1289

    
1290
	$('#csrtosign').change(function () {
1291
		set_csr_ro();
1292
	});
1293

    
1294
	// ---------- On initial page load ------------------------------------------------------------
1295

    
1296
	internalca_change();
1297
	set_csr_ro();
1298

    
1299
	// Suppress "Delete row" button if there are fewer than two rows
1300
	checkLastRow();
1301

    
1302
<?php endif; ?>
1303

    
1304

    
1305
});
1306
//]]>
1307
</script>
1308
<?php
1309
include('foot.inc');
(191-191/223)