Merge pull request #362 from terrafrost/spkac

X509: add signSPKAC() and saveSPKAC() methods

* terrafrost/spkac:
  X509: Unit test CS changes
  X509: move location of SPKAC unit test
  X509: CS adjustments
  X509: PHP4 compat changes
  RSA: PHP4 compat changes
  X509: rm trailing white space from unit test
  X509: add SPKAC unit test
  X509: add signSPKAC() and saveSPKAC() methods
This commit is contained in:
Andreas Fischer 2014-06-05 16:56:34 +02:00
commit 25d31e5867
3 changed files with 236 additions and 2 deletions

View File

@ -474,6 +474,10 @@ class Crypt_RSA
} }
switch ( !defined('CRYPT_RSA_MODE') ) { // ie. only run this if the above didn't set CRYPT_RSA_MODE already switch ( !defined('CRYPT_RSA_MODE') ) { // ie. only run this if the above didn't set CRYPT_RSA_MODE already
// openssl_pkey_get_details - which is used in the only place Crypt/RSA.php uses OpenSSL - was introduced in PHP 5.2.0
case !function_exists('openssl_pkey_get_details'):
define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_INTERNAL);
break;
case extension_loaded('openssl') && version_compare(PHP_VERSION, '4.2.0', '>=') && file_exists($this->configFile): case extension_loaded('openssl') && version_compare(PHP_VERSION, '4.2.0', '>=') && file_exists($this->configFile):
// some versions of XAMPP have mismatched versions of OpenSSL which causes it not to work // some versions of XAMPP have mismatched versions of OpenSSL which causes it not to work
ob_start(); ob_start();

View File

@ -297,6 +297,14 @@ class File_X509
*/ */
var $caFlag = false; var $caFlag = false;
/**
* SPKAC Challenge
*
* @var String
* @access private
*/
var $challenge;
/** /**
* Default Constructor. * Default Constructor.
* *
@ -2501,7 +2509,8 @@ class File_X509
$asn1->loadFilters($filters); $asn1->loadFilters($filters);
$result = ''; $result = '';
foreach ($dn['rdnSequence'] as $rdn) { foreach ($dn['rdnSequence'] as $rdn) {
foreach ($rdn as &$attr) { foreach ($rdn as $i=>$attr) {
$attr = &$rdn[$i];
if (is_array($attr['value'])) { if (is_array($attr['value'])) {
foreach ($attr['value'] as $type => $v) { foreach ($attr['value'] as $type => $v) {
$type = array_search($type, $asn1->ANYmap, true); $type = array_search($type, $asn1->ANYmap, true);
@ -2759,6 +2768,19 @@ class File_X509
$this->privateKey = $key; $this->privateKey = $key;
} }
/**
* Set challenge
*
* Used for SPKAC CSR's
*
* @param String $challenge
* @access public
*/
function setChallenge($challenge)
{
$this->challenge = $challenge;
}
/** /**
* Gets the public key * Gets the public key
* *
@ -2952,7 +2974,8 @@ class File_X509
$asn1 = new File_ASN1(); $asn1 = new File_ASN1();
$temp = preg_replace('#(?:^[^=]+=)|[\r\n\\\]#', '', $spkac); // OpenSSL produces SPKAC's that are preceeded by the string SPKAC=
$temp = preg_replace('#(?:SPKAC=)|[ \r\n\\\]#', '', $spkac);
$temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false; $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false;
if ($temp != false) { if ($temp != false) {
$spkac = $temp; $spkac = $temp;
@ -3004,6 +3027,49 @@ class File_X509
return $spkac; return $spkac;
} }
/**
* Save a SPKAC CSR request
*
* @param Array $csr
* @param Integer $format optional
* @access public
* @return String
*/
function saveSPKAC($spkac, $format = FILE_X509_FORMAT_PEM)
{
if (!is_array($spkac) || !isset($spkac['publicKeyAndChallenge'])) {
return false;
}
$algorithm = $this->_subArray($spkac, 'publicKeyAndChallenge/spki/algorithm/algorithm');
switch (true) {
case !$algorithm:
case is_object($spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']);
break;
default:
switch ($algorithm) {
case 'rsaEncryption':
$spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']
= base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'])));
}
}
$asn1 = new File_ASN1();
$asn1->loadOIDs($this->oids);
$spkac = $asn1->encodeDER($spkac, $this->SignedPublicKeyAndChallenge);
switch ($format) {
case FILE_X509_FORMAT_DER:
return $spkac;
// case FILE_X509_FORMAT_PEM:
default:
// OpenSSL's implementation of SPKAC requires the SPKAC be preceeded by SPKAC= and since there are pretty much
// no other SPKAC decoders phpseclib will use that same format
return 'SPKAC=' . base64_encode($spkac);
}
}
/** /**
* Load a Certificate Revocation List * Load a Certificate Revocation List
* *
@ -3368,6 +3434,71 @@ class File_X509
return $result; return $result;
} }
/**
* Sign a SPKAC
*
* @access public
* @return Mixed
*/
function signSPKAC($signatureAlgorithm = 'sha1WithRSAEncryption')
{
if (!is_object($this->privateKey)) {
return false;
}
$origPublicKey = $this->publicKey;
$class = get_class($this->privateKey);
$this->publicKey = new $class();
$this->publicKey->loadKey($this->privateKey->getPublicKey());
$this->publicKey->setPublicKey();
$publicKey = $this->_formatSubjectPublicKey();
if (!$publicKey) {
return false;
}
$this->publicKey = $origPublicKey;
$currentCert = isset($this->currentCert) ? $this->currentCert : null;
$signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null;
// re-signing a SPKAC seems silly but since everything else supports re-signing why not?
if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['publicKeyAndChallenge'])) {
$this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
$this->currentCert['publicKeyAndChallenge']['spki'] = $publicKey;
if (!empty($this->challenge)) {
// the bitwise AND ensures that the output is a valid IA5String
$this->currentCert['publicKeyAndChallenge']['challenge'] = $this->challenge & str_repeat("\x7F", strlen($this->challenge));
}
} else {
$this->currentCert = array(
'publicKeyAndChallenge' =>
array(
'spki' => $publicKey,
// quoting <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/keygen>,
// "A challenge string that is submitted along with the public key. Defaults to an empty string if not specified."
// both Firefox and OpenSSL ("openssl spkac -key private.key") behave this way
// we could alternatively do this instead if we ignored the specs:
// crypt_random_string(8) & str_repeat("\x7F", 8)
'challenge' => !empty($this->challenge) ? $this->challenge : ''
),
'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
'signature' => false // this is going to be overwritten later
);
}
// resync $this->signatureSubject
// save $publicKeyAndChallenge in case there are any File_ASN1_Element objects in it
$publicKeyAndChallenge = $this->currentCert['publicKeyAndChallenge'];
$this->loadSPKAC($this->saveSPKAC($this->currentCert));
$result = $this->_sign($this->privateKey, $signatureAlgorithm);
$result['publicKeyAndChallenge'] = $publicKeyAndChallenge;
$this->currentCert = $currentCert;
$this->signatureSubject = $signatureSubject;
return $result;
}
/** /**
* Sign a CRL * Sign a CRL
* *

View File

@ -0,0 +1,99 @@
<?php
/**
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMXIV Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
*/
require_once 'File/X509.php';
require_once 'Crypt/RSA.php';
class Unit_File_X509_SPKACTest extends PhpseclibTestCase
{
public function testLoadSPKAC()
{
$test = 'MIICQDCCASgwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQChgo9mWzQm3TSwGgpZnIc54' .
'TZ8gYpfAO/AI0etvyWDqnFfdNCUQsqxTdSi6/rtrJdLGBsszRGrRIc/0JqmjM+jCHGYutLeo4xwgr' .
'a3HAZrWDypL5IlRWnLmLA4U/qGXCXNSk+9NrJl39X3IDA8o/aOJyr9iMUJMvswcWjVjPom3NhAgmJ' .
'ZwW0vUEMw9zszExpiRnGSO5XXntQW2qvfzo+J3NzS3BBbKxEmTsfOLHextcXeFQUaBQHXB/WOtweW' .
'Y/Bd4iZ8ETmhal28g1HWVcTFPD+V+KPRFeARlVEW6JmcJucW2WdJlBGKXXXPEfdHrDS3OgD/eDWfM' .
'JE4mChZ/icxAgMBAAEWADANBgkqhkiG9w0BAQQFAAOCAQEAUMvIKhlSgEgbC081b/FJwh6mbuVgYN' .
'ZV37Ts2WjrHoDFlabu9WXU8xzgaXct3sO51vJM5I36rY4UPyc6w3y9dLaamEwKUoWnpHG8mlXs2JG' .
'GEUOvxh5z9yfk/2ZmdCVBlKnU1LDB+ZDyNyNh5B0YULrJKw9e0jV+ymP7srwUSBcdUfZh1KEKGVIN' .
'Uv4J3GuL8V63E2unWCHGRPw4EmFVTbWpgMx96XR7p/pMavu6/pVKgYQqWLOmEeOK+dmT/QVon28d5' .
'dmeL7aWrpP+3x3L0A9cATksracQX676XogdAEXJ59fcr/S5AGw1TFErbyBbfyeAWvzDZIXeMXpb9h' .
'yNtA==';
$x509 = new File_X509();
$spkac = $x509->loadSPKAC($test);
$this->assertInternalType('array', $spkac);
$spkac = $x509->loadSPKAC('SPKAC=' . $test);
$this->assertInternalType('array', $spkac);
$this->assertTrue(
$x509->validateSignature(),
'Failed asserting that the signature is valid'
);
$pubKey = $x509->getPublicKey();
$this->assertInternalType('string', "$pubKey");
}
public function testSaveSPKAC()
{
$privKey = new Crypt_RSA();
extract($privKey->createKey());
$privKey->loadKey($privatekey);
$x509 = new File_X509();
$x509->setPrivateKey($privKey);
$x509->setChallenge('...');
$spkac = $x509->signSPKAC();
$this->assertInternalType('array', $spkac);
$this->assertInternalType('string', $x509->saveSPKAC($spkac));
$x509 = new File_X509();
$x509->setPrivateKey($privKey);
$spkac = $x509->signSPKAC();
$this->assertInternalType('array', $spkac);
$this->assertInternalType('string', $x509->saveSPKAC($spkac));
}
public function testBadSignatureSPKAC()
{
$test = 'MIICQDCCASgwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQChgo9mWzQm3TSwGgpZnIc54' .
'TZ8gYpfAO/AI0etvyWDqnFfdNCUQsqxTdSi6/rtrJdLGBsszRGrRIc/0JqmjM+jCHGYutLeo4xwgr' .
'a3HAZrWDypL5IlRWnLmLA4U/qGXCXNSk+9NrJl39X3IDA8o/aOJyr9iMUJMvswcWjVjPom3NhAgmJ' .
'ZwW0vUEMw9zszExpiRnGSO5XXntQW2qvfzo+J3NzS3BBbKxEmTsfOLHextcXeFQUaBQHXB/WOtweW' .
'Y/Bd4iZ8ETmhal28g1HWVcTFPD+V+KPRFeARlVEW6JmcJucW2WdJlBGKXXXPEfdHrDS3OgD/eDWfM' .
'JE4mChZ/icxAgMBAAEWADANBgkqhkiG9w0BAQQFAAOCAQEAUMvIKhlSgEgbC081b/FJwh6mbuVgYN' .
'ZV37Ts2WjrHoDFlabu9WXU8xzgaXct3sO51vJM5I36rY4UPyc6w3y9dLaamEwKUoWnpHG8mlXs2JG' .
'GEUOvxh5z9yfk/2ZmdCVBlKnU1LDB+ZDyNyNh5B0YULrJKw9e0jV+ymP7srwUSBcdUfZh1KEKGVIN' .
'Uv4J3GuL8V63E2unWCHGRPw4EmFVTbWpgMx96XR7p/pMavu6/pVKgYQqWLOmEeOK+dmT/QVon28d5' .
'dmeL7aWrpP+3x3L0A9cATksracQX676XogdAEXJ59fcr/S5AGw1TFErbyBbfyeAWvzDZIXeMXpb9h' .
'yNtA==';
$x509 = new File_X509();
$spkac = $x509->loadSPKAC($test);
$spkac['publicKeyAndChallenge']['challenge'] = 'zzzz';
$x509->loadSPKAC($x509->saveSPKAC($spkac));
$this->assertFalse(
$x509->validateSignature(),
'Failed asserting that the signature is invalid'
);
}
}