* @license BSD */ //namespace Domframework; /** 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 (); /** The basic openssl.cnf configuration */ private $opensslConf = 'HOME = . RANDFILE = $ENV::HOME/.rnd [ req ] distinguished_name = req_distinguished_name [ req_distinguished_name ] [ v3_ca ] subjectKeyIdentifier=hash authorityKeyIdentifier=keyid:always,issuer basicConstraints = critical,CA:true keyUsage = cRLSign, keyCertSign [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment extendedKeyUsage = serverAuth, clientAuth '; // }}} /** Check if openssl support is available in PHP */ public function __construct () // {{{ { if (! function_exists ("openssl_csr_new")) throw new \Exception (dgettext ("domframework", "No openssl support in PHP"), 500); $this->opensslCnfPath = tempnam ("/tmp", "openssl-"); file_put_contents ($this->opensslCnfPath, $this->opensslConf); $this->configargs = array ( "config" => $this->opensslCnfPath, "digest_alg" => "sha256WithRSAEncryption", "private_key_bits" => 4096, ); } // }}} /** Remove the temporary files when destroying the object */ 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); if ($req_cert === false) throw new \Exception ("Can not create CA certificate : " . openssl_error_string (), 500); openssl_x509_export ($req_cert, $this->caCert); openssl_pkey_export ($req_key, $this->caKey); return $this; } // }}} /** Get/Set the ca cert * @param string|null $caCert 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 $caKey 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 $privateKey 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. * Will create a private key if none is already exists * @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 : ". openssl_error_string (), 500); openssl_csr_export ($req_csr, $out); return $out; } // }}} /** Sign a CSR with an CA cert/key pair 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) * @param array|null $altNames The alternative names allowed in cert * @return the signed certificate in PEM */ public function signCSR ($csr, $caCert, $caKey, $days = 365, $altNames = array ()) // {{{ { $conf = $this->opensslConf; if (! empty($altNames)) { // Copy the commonName from CSR request into subjectAltName $subject = openssl_csr_get_subject($csr); if (key_exists ("CN", $subject)) $commonName = $subject["CN"]; else throw new \Exception ("Can not get the CN from CSR", 500); // Add all the alternateNames $conf .= "subjectAltName = @san\n\n[ san ]\n"; $conf .= "DNS.0 = $commonName\n"; foreach ($altNames as $nb => $name) { $conf .= "DNS.".($nb+1)." = $name\n"; } file_put_contents ($this->opensslCnfPath, $conf); } $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); file_put_contents ($this->opensslCnfPath, $this->opensslConf); return $certout; } // }}} }