diff --git a/Tests/certificationauthorityTest.php b/Tests/certificationauthorityTest.php new file mode 100644 index 0000000..1f535ba --- /dev/null +++ b/Tests/certificationauthorityTest.php @@ -0,0 +1,113 @@ +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); + } +} diff --git a/certificationauthority.php b/certificationauthority.php new file mode 100644 index 0000000..fe4b004 --- /dev/null +++ b/certificationauthority.php @@ -0,0 +1,222 @@ + + */ + +/** 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; + } + // }}} +}