Project

General

Profile

« Previous | Next » 

Revision 9efec277

Added by Jim Pingle almost 3 years ago

Convert P12 export to OpenSSL. Fixes #13257

PHP native method of creating PKCS#12 archives does not support using specific algorithms for encryption, so use the openssl binary instead.

Use AES-256 and SHA256 when encrypting the PKCS#12 data and private key.

View differences:

src/etc/inc/certs.inc
2606 2606
	return $list;
2607 2607
}
2608 2608

  
2609
/****f* certs/cert_pkcs12_export
2610
 * NAME
2611
 *   cert_pkcs12_export - Export a PKCS#12 archive file for a given certificate
2612
 *                        and optional CA and passphrase.
2613
 * INPUTS
2614
 *   $cert      : Certificate entry array.
2615
 *   $passphrase: Optional passphrase used to encrypt the archive contents and
2616
 *                private key.
2617
 *   $add_ca    : Boolean flag which determines whether or not the certificate
2618
 *                CA is included in the archive (if available)
2619
 *   $delivery  : Delivery method for the result: "file", "download", or "data".
2620
 *                See RESULT section for details.
2621
 * RESULT
2622
 *   Returns false on failure, otherwise result depends upon the value passed in
2623
 *   $delivery:
2624
 *       "file"    : Returns the path to the output archive file.
2625
 *                   NOTE: Does not clean up path, caller must clean up the
2626
 *                         entire directory containing the output file.
2627
 *       "download": Sends the archive data to the current GUI browser session.
2628
 *                   Must be called before any output is sent to the user
2629
 *                   session.
2630
 *       "data"    : Returns the contents of the PKCS#12 archive as a string.
2631
 * NOTES
2632
 *   If the certificate entry does not contain a private key, the archive will
2633
 *   also not contain a key.
2634
 ******/
2635

  
2636
function cert_pkcs12_export($cert, $passphrase = '', $add_ca = true, $delivery = 'download') {
2637
	global $g;
2638

  
2639
	/* Unusable certificate entry, bail early. */
2640
	if (!is_array($cert) || empty($cert)) {
2641
		return false;
2642
	}
2643

  
2644
	/* Encryption and Digest */
2645
	$algo = '-aes256 -certpbe AES-256-CBC -keypbe AES-256-CBC';
2646
	$hash = '-macalg sha256';
2647

  
2648
	/* Make a secure temporary directory */
2649
	$workdir = tempnam("{$g['tmp_path']}/", "p12export");
2650
	@unlink_if_exists($workdir);
2651
	mkdir($workdir, 0600);
2652

  
2653
	/* Set the friendly name to the certificate description, if available */
2654
	$descr = "";
2655
	if (!empty($cert['descr'])) {
2656
		$edescr = escapeshellarg($cert['descr']);
2657
		$descr = "-name {$edescr} -CSP {$edescr}";
2658
		$fileprefix = basename($cert['descr']);
2659
	}
2660

  
2661
	/* If there isn't a usable portion of the description, use the refid */
2662
	if (empty($fileprefix)) {
2663
		$fileprefix = $cert['refid'];
2664
	}
2665

  
2666
	/* Exported output archive filename */
2667
	$outpath = "{$workdir}/{$fileprefix}.p12";
2668
	$eoutpath = escapeshellarg($outpath);
2669

  
2670
	/* Passphrase handling */
2671
	if (!empty($passphrase)) {
2672
		/* Use passphrase text file so the passphrase is not visible in
2673
		 * process list. */
2674
		$passfile = "{$workdir}/passphrase.txt";
2675
		file_put_contents($passfile, $passphrase . "\n");
2676
		$pass = '-passout file:' . escapeshellarg($passfile);
2677
	} else {
2678
		/* Null password + disable encryption of the keys */
2679
		$pass = '-passout pass: -nodes';
2680
	}
2681

  
2682
	/* Certificate file */
2683
	$crtpath = "{$workdir}/cert.pem";
2684
	$ecrtpath = escapeshellarg($crtpath);
2685
	file_put_contents($crtpath, base64_decode($cert['crt']));
2686

  
2687
	/* Private key (if present) */
2688
	if (!empty($cert['prv'])) {
2689
		$keypath = "{$workdir}/key.pem";
2690
		/* Write key to a secure temporary name */
2691
		file_put_contents($keypath, base64_decode($cert['prv']));
2692
		$key = '-inkey ' . escapeshellarg($keypath);
2693
	} else {
2694
		$key = '-nokeys';
2695
	}
2696

  
2697
	/* Add CA if one is defined and requested */
2698
	$eca = '';
2699
	if ($add_ca && !empty($cert['caref'])) {
2700
		$ca = lookup_ca($cert['caref']);
2701
		if ($ca) {
2702
			$capath = "{$workdir}/ca.pem";
2703
			file_put_contents($capath, base64_decode($ca['crt']));
2704
			$eca = '-certfile ' . escapeshellarg($capath);
2705
		}
2706
	}
2707

  
2708
	/* Export a PKCS#12 archive using these components and settings */
2709
	exec("/usr/bin/openssl pkcs12 -export -in {$ecrtpath} {$eca} {$key} -out {$eoutpath} {$pass} {$descr} {$algo} {$hash}");
2710

  
2711
	/* Bail if the output is invalid */
2712
	if (!file_exists($outpath) ||
2713
	    (filesize($outpath) == 0)) {
2714
		return false;
2715
	}
2716

  
2717
	/* Tailor output as requested by the caller */
2718
	switch ($delivery) {
2719
		case 'file':
2720
			/* Return path to export file, do not clean up, caller must clean up. */
2721
			return $outpath;
2722
			break;
2723
		case 'download':
2724
			/* Send file to user and cleanup */
2725
			$p12_data = file_get_contents($outpath);
2726
			rmdir_recursive($workdir);
2727
			send_user_download('data', $p12_data, "{$cert['descr']}.p12");
2728
			return true;
2729
			break;
2730
		case 'data':
2731
		default:
2732
			/* Return PKCS#12 archive data and cleanup */
2733
			$p12_data = file_get_contents($outpath);
2734
			rmdir_recursive($workdir);
2735
			return $p12_data;
2736
			break;
2737
	}
2738

  
2739
	return null;
2740
}
2609 2741
?>
src/usr/local/www/system_certmanager.php
195 195
		} else {
196 196
			$password = null;
197 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");
198
		cert_pkcs12_export($thiscert, $password, true, 'download');
211 199
		break;
212 200
	default:
213 201
		break;
......
1473 1461
							<a href="system_certmanager.php?act=exp&amp;id=<?=$cert['refid']?>" class="fa fa-certificate" title="<?=gettext("Export Certificate")?>"></a>
1474 1462
							<?php if ($cert['prv']): ?>
1475 1463
								<a href="system_certmanager.php?act=key&amp;id=<?=$cert['refid']?>" class="fa fa-key" title="<?=gettext("Export Key")?>"></a>
1476
								<a href="system_certmanager.php?act=p12&amp;id=<?=$cert['refid']?>" class="fa fa-archive" title="<?=gettext("Export P12")?>"></a>
1464
								<a href="system_certmanager.php?act=p12&amp;id=<?=$cert['refid']?>" class="fa fa-archive" title="<?=gettext("Export PCKS#12 Archive without Encryption")?>"></a>
1477 1465
							<?php endif?>
1478 1466
							<?php if (is_cert_locally_renewable($cert)): ?>
1479 1467
								<a href="system_certmanager_renew.php?type=cert&amp;refid=<?=$cert['refid']?>" class="fa fa-repeat" title="<?=gettext("Reissue/Renew")?>"></a>

Also available in: Unified diff