Add certification authority support
git-svn-id: https://svn.fournier38.fr/svn/ProgSVN/trunk@5107 bf3deb0d-5f1a-0410-827f-c0cc1f45334c
This commit is contained in:
113
Tests/certificationauthorityTest.php
Normal file
113
Tests/certificationauthorityTest.php
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/** Test the certification Authority
|
||||||
|
*/
|
||||||
|
class test_certificationauthority extends PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
public function test_createCA_1 ()
|
||||||
|
{
|
||||||
|
$certificationauthority = new certificationauthority ();
|
||||||
|
$certificationauthority->createCA ("FR", "FOURNIER38", "CATEST");
|
||||||
|
$caCert = explode ("\n", $certificationauthority->caCert ());
|
||||||
|
$caKey = explode ("\n", $certificationauthority->caKey ());
|
||||||
|
$res = $caCert[0] . $caKey[0];
|
||||||
|
$this->assertSame ($res,
|
||||||
|
"-----BEGIN CERTIFICATE----------BEGIN PRIVATE KEY-----");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_createPK_1 ()
|
||||||
|
{
|
||||||
|
$certificationauthority = new certificationauthority ();
|
||||||
|
$privateKey = $certificationauthority->createPrivateKey () -> privateKey ();
|
||||||
|
$privateKey = explode ("\n", $privateKey);
|
||||||
|
$this->assertSame ($privateKey[0], "-----BEGIN PRIVATE KEY-----");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_createCSR_1 ()
|
||||||
|
{
|
||||||
|
$certificationauthority = new certificationauthority ();
|
||||||
|
$csr = $certificationauthority->createCSR ("FR", "FOURNIER38", "CSR");
|
||||||
|
$csr = explode ("\n", $csr);
|
||||||
|
$this->assertSame ($csr[0], "-----BEGIN CERTIFICATE REQUEST-----");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_signCSR_1 ()
|
||||||
|
{
|
||||||
|
$certificationauthority = new certificationauthority ();
|
||||||
|
$certificationauthority->createCA ("FR", "FOURNIER38", "CATEST");
|
||||||
|
$caCert = $certificationauthority->caCert ();
|
||||||
|
$caKey = $certificationauthority->caKey ();
|
||||||
|
$csr = $certificationauthority->createCSR ("FR", "FOURNIER38", "CSR");
|
||||||
|
$cert = $certificationauthority->signCSR ($csr, $caCert, $caKey);
|
||||||
|
$cert = explode ("\n", $cert);
|
||||||
|
$this->assertSame ($cert[0], "-----BEGIN CERTIFICATE-----");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_signCSR_2 ()
|
||||||
|
{
|
||||||
|
$certificationauthority = new certificationauthority ();
|
||||||
|
$certificationauthority->createCA ("FR", "FOURNIER38", "CATEST");
|
||||||
|
$caCert = $certificationauthority->caCert ();
|
||||||
|
$caKey = $certificationauthority->caKey ();
|
||||||
|
$csr = $certificationauthority->createCSR ("FR", "FOURNIER38", "CSR");
|
||||||
|
$cert = $certificationauthority->signCSR ($csr, $caCert, $caKey);
|
||||||
|
file_put_contents ("/tmp/test_signCSR_2", $cert);
|
||||||
|
exec ("openssl x509 -in - -text -noout < /tmp/test_signCSR_2", $output);
|
||||||
|
$res = preg_match ("#Subject: C = FR, .+ CN = CSR#",
|
||||||
|
implode ("\n", $output));
|
||||||
|
unlink ("/tmp/test_signCSR_2");
|
||||||
|
$this->assertSame ($res, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_signCSR_3 ()
|
||||||
|
{
|
||||||
|
// Check if generated cert X509v3 Extended Key Usage are valid
|
||||||
|
$certificationauthority = new certificationauthority ();
|
||||||
|
$certificationauthority->createCA ("FR", "FOURNIER38", "CATEST");
|
||||||
|
$caCert = $certificationauthority->caCert ();
|
||||||
|
$caKey = $certificationauthority->caKey ();
|
||||||
|
$csr = $certificationauthority->createCSR ("FR", "FOURNIER38", "CSR");
|
||||||
|
$cert = $certificationauthority->signCSR ($csr, $caCert, $caKey);
|
||||||
|
file_put_contents ("/tmp/test_signCSR_3", $cert);
|
||||||
|
exec ("openssl x509 -in - -text -noout < /tmp/test_signCSR_3", $output);
|
||||||
|
$res = preg_match (
|
||||||
|
"#TLS Web Server Authentication, TLS Web Client Authentication#",
|
||||||
|
implode ("\n", $output));
|
||||||
|
unlink ("/tmp/test_signCSR_3");
|
||||||
|
$this->assertSame ($res, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_signCSR_4 ()
|
||||||
|
{
|
||||||
|
// Check if generated cert issuer name is valid
|
||||||
|
$certificationauthority = new certificationauthority ();
|
||||||
|
$certificationauthority->createCA ("FR", "FOURNIER38", "CATEST");
|
||||||
|
$caCert = $certificationauthority->caCert ();
|
||||||
|
$caKey = $certificationauthority->caKey ();
|
||||||
|
$csr = $certificationauthority->createCSR ("FR", "FOURNIER38", "CSR");
|
||||||
|
$cert = $certificationauthority->signCSR ($csr, $caCert, $caKey);
|
||||||
|
file_put_contents ("/tmp/test_signCSR_4", $cert);
|
||||||
|
exec ("openssl x509 -in - -text -noout < /tmp/test_signCSR_4", $output);
|
||||||
|
$res = preg_match ("#Issuer: C = FR, O = FOURNIER38, CN = CATEST#",
|
||||||
|
implode ("\n", $output));
|
||||||
|
unlink ("/tmp/test_signCSR_4");
|
||||||
|
$this->assertSame ($res, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_signCSR_5 ()
|
||||||
|
{
|
||||||
|
// Check if generated cert is not tagged CA
|
||||||
|
$certificationauthority = new certificationauthority ();
|
||||||
|
$certificationauthority->createCA ("FR", "FOURNIER38", "CATEST");
|
||||||
|
$caCert = $certificationauthority->caCert ();
|
||||||
|
$caKey = $certificationauthority->caKey ();
|
||||||
|
$csr = $certificationauthority->createCSR ("FR", "FOURNIER38", "CSR");
|
||||||
|
$cert = $certificationauthority->signCSR ($csr, $caCert, $caKey);
|
||||||
|
file_put_contents ("/tmp/test_signCSR_5", $cert);
|
||||||
|
exec ("openssl x509 -in - -text -noout < /tmp/test_signCSR_5", $output);
|
||||||
|
$res = preg_match ("# CA:FALSE#",
|
||||||
|
implode ("\n", $output));
|
||||||
|
unlink ("/tmp/test_signCSR_5");
|
||||||
|
$this->assertSame ($res, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
222
certificationauthority.php
Normal file
222
certificationauthority.php
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
<?php
|
||||||
|
/** DomFirewall
|
||||||
|
* @package domfirewall
|
||||||
|
* @author Dominique Fournier <dominique@fournier38.fr>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** An certificate authority
|
||||||
|
*/
|
||||||
|
class certificationauthority
|
||||||
|
{
|
||||||
|
// PROPERTIES
|
||||||
|
/** The opensslCnfPath file
|
||||||
|
*/
|
||||||
|
private $opensslCnfPath;
|
||||||
|
|
||||||
|
/** The CA public cert
|
||||||
|
*/
|
||||||
|
private $caCert = "";
|
||||||
|
/** The CA private key
|
||||||
|
*/
|
||||||
|
private $caKey = "";
|
||||||
|
|
||||||
|
/** The user private key resource
|
||||||
|
*/
|
||||||
|
private $privateKey;
|
||||||
|
|
||||||
|
/** The configuration arguments
|
||||||
|
*/
|
||||||
|
private $configargs = array ();
|
||||||
|
|
||||||
|
/** Check if /usr/bin/openssl is available */
|
||||||
|
public function __construct ()
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
if (! function_exists ("openssl_csr_new"))
|
||||||
|
throw new \Exception (_("No openssl support in PHP"),
|
||||||
|
500);
|
||||||
|
$this->opensslCnfPath = tempnam ("/tmp", "openssl-");
|
||||||
|
file_put_contents ($this->opensslCnfPath,
|
||||||
|
'HOME = .
|
||||||
|
RANDFILE = $ENV::HOME/.rnd
|
||||||
|
|
||||||
|
[ req ]
|
||||||
|
distinguished_name = req_distinguished_name
|
||||||
|
|
||||||
|
[ req_distinguished_name ]
|
||||||
|
|
||||||
|
[ v3_req ]
|
||||||
|
basicConstraints = CA:FALSE
|
||||||
|
keyUsage = digitalSignature, keyEncipherment
|
||||||
|
extendedKeyUsage = serverAuth, clientAuth
|
||||||
|
|
||||||
|
[ v3_ca ]
|
||||||
|
subjectKeyIdentifier=hash
|
||||||
|
authorityKeyIdentifier=keyid:always,issuer
|
||||||
|
basicConstraints = critical,CA:true
|
||||||
|
keyUsage = cRLSign, keyCertSign
|
||||||
|
');
|
||||||
|
$this->configargs = array (
|
||||||
|
"config" => $this->opensslCnfPath,
|
||||||
|
"digest_alg" => "sha256WithRSAEncryption",
|
||||||
|
"private_key_bits" => 4096,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
public function __destruct ()
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
if (file_exists ($this->opensslCnfPath))
|
||||||
|
unlink ($this->opensslCnfPath);
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
/** Create the pair key/cert for authority
|
||||||
|
* @param string $countryName Country name (like FR)
|
||||||
|
* @param string $organizationName Name of organization
|
||||||
|
* @param string $commonName Common name of authority
|
||||||
|
* @param integer|null $days The number of days of validity of the CA (3650
|
||||||
|
* by default)
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function createCA ($countryName, $organizationName, $commonName,
|
||||||
|
$days = 3650)
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
$req_key = openssl_pkey_new (array (
|
||||||
|
"config" => $this->opensslCnfPath,
|
||||||
|
"private_key_bits" => 4096,
|
||||||
|
"private_key_type" => OPENSSL_KEYTYPE_RSA,
|
||||||
|
));
|
||||||
|
$dn = array (
|
||||||
|
"countryName" => $countryName,
|
||||||
|
"organizationName" => $organizationName,
|
||||||
|
"commonName" => $commonName,
|
||||||
|
);
|
||||||
|
$this->configargs["x509_extensions"] = "v3_ca";
|
||||||
|
// x509_extensions must be defined in /etc/ssl/openssl.cnf
|
||||||
|
$req_csr = openssl_csr_new ($dn, $req_key, $this->configargs);
|
||||||
|
$req_cert = openssl_csr_sign ($req_csr, NULL, $req_key, $days,
|
||||||
|
$this->configargs);
|
||||||
|
openssl_x509_export ($req_cert, $this->caCert);
|
||||||
|
openssl_pkey_export ($req_key, $this->caKey);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
/** Get/Set the ca cert
|
||||||
|
* @param string|null The CA cert to get/set
|
||||||
|
* @return the CA if get in PEM, $this if set
|
||||||
|
*/
|
||||||
|
public function caCert ($caCert = null)
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
if ($caCert === null)
|
||||||
|
return $this->caCert;
|
||||||
|
if (! is_string ($caCert))
|
||||||
|
throw new \Exception ("AC : Invalid caCert provided : not a string", 500);
|
||||||
|
$this->caCert = $caCert;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
/** Get/Set the ca key
|
||||||
|
* @param string|null The CA key to get/set
|
||||||
|
* @return the CA if get, $this if set
|
||||||
|
*/
|
||||||
|
public function caKey ($caKey = null)
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
if ($caKey === null)
|
||||||
|
return $this->caKey;
|
||||||
|
if (! is_string ($caKey))
|
||||||
|
throw new \Exception ("AC : invalid caKey provided : not a string", 500);
|
||||||
|
$this->caKey = $caKey;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
/** Create a private key
|
||||||
|
* @return $this;
|
||||||
|
*/
|
||||||
|
public function createPrivateKey ()
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
$this->privateKey = openssl_pkey_new (array (
|
||||||
|
"config" => $this->opensslCnfPath,
|
||||||
|
"private_key_bits" => 4096,
|
||||||
|
"private_key_type" => OPENSSL_KEYTYPE_RSA,
|
||||||
|
));
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
/** Get in PEM/Set the private key
|
||||||
|
* @param string|null The private key to use
|
||||||
|
* @return the privatekey if get in PEM, $this if set
|
||||||
|
*/
|
||||||
|
public function privateKey ($privateKey = null)
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
if ($privateKey === null)
|
||||||
|
{
|
||||||
|
openssl_pkey_export($this->privateKey, $out);
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
if (! is_string ($privateKey))
|
||||||
|
throw new \Exception ("AC : invalid privateKey provided : not a string",
|
||||||
|
500);
|
||||||
|
$this->privateKey = openssl_pkey_get_private ($privateKey);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
/** Create a CSR
|
||||||
|
* @param string $countryName Country name (like FR)
|
||||||
|
* @param string $organizationName Name of organization
|
||||||
|
* @param string $commonName Common name of authority
|
||||||
|
* @return the CSR created in PEM
|
||||||
|
*/
|
||||||
|
public function createCSR ($countryName, $organizationName, $commonName)
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
if ($this->privateKey === null)
|
||||||
|
$this->createPrivateKey ();
|
||||||
|
$dn = array (
|
||||||
|
"countryName" => $countryName,
|
||||||
|
"organizationName" => $organizationName,
|
||||||
|
"commonName" => $commonName,
|
||||||
|
);
|
||||||
|
$this->configargs["x509_extensions"] = "v3_req";
|
||||||
|
$req_csr = openssl_csr_new ($dn, $this->privateKey, $this->configargs);
|
||||||
|
if ($req_csr === false)
|
||||||
|
throw new \Exception ("CA : Can not generate a CSR", 500);
|
||||||
|
openssl_csr_export ($req_csr, $out);
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
/** Sign a CSR with an CA cert and key and return the signed certificate in
|
||||||
|
* PEM mode
|
||||||
|
* The caCert and caKey must be defined
|
||||||
|
* @param string $csr The CSR to sign
|
||||||
|
* @param string $caCert The CA Certificate
|
||||||
|
* @param string $caKey The CA private key
|
||||||
|
* @param integer|null $days The number of days of validity (365 by default)
|
||||||
|
* @return the signed certificate in PEM
|
||||||
|
*/
|
||||||
|
public function signCSR ($csr, $caCert, $caKey, $days = 365)
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
$this->configargs["x509_extensions"] = "v3_req";
|
||||||
|
$usercert = openssl_csr_sign ($csr, $caCert, $caKey, $days,
|
||||||
|
$this->configargs, date ("YmdHis"));
|
||||||
|
if ($usercert === false)
|
||||||
|
throw new \Exception ("Can not create certificate : " .
|
||||||
|
openssl_error_string (), 500);
|
||||||
|
openssl_x509_export($usercert, $certout);
|
||||||
|
return $certout;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user