last_certificate_id=variable_get('last_certificate_id',0,'Latest certificate ID parsed by update_domains. Do not change this unless you know what you are doing'); $this->default_certificate_fqdn=variable_get('default_certificate_fqdn',$L_FQDN,'FQDN of the certificate we will use as a default one before getting a proper one through any provider. If unsure, keep the default'); } // ----------------------------------------------------------------- /** * Return the list of special FQDN for which we'd like to obtain a certificate too. * (apart from sub+domaine from sub_domaines table) * used by providers to get the certs they should generate * also used by update_domaines to choose which cert to use for a specific fqdn */ function get_fqdn_specials() { global $L_FQDN; $specials=array($L_FQDN); $variables=array("fqdn_dovecot","fqdn_postfix","fqdn_proftpd","fqdn_mailman"); foreach($variables as $var) { $value = variable_get($var,null); if ($value && !in_array($value,$specials)) { $specials[]=$value; } } return $specials; } // ----------------------------------------------------------------- /** * set expired certificates as such : */ function expire_certificates() { global $db; $db->query("UPDATE certificates SET status=".self::STATUS_EXPIRED." WHERE status=".self::STATUS_OK." AND validendquery("SELECT max(id) AS maxid FROM certificates;"); if (!$db->next_record()) { $msg->raise("ERROR","ssl",_("FATAL: no certificates in certificates table, even the SnakeOil one??")); return false; } $maxid=$db->Record["maxid"]; if ($maxid>$this->last_certificate_id) { $db->query("SELECT id,fqdn,altnames,sslcrt FROM certificates WHERE id>?",array($this->last_certificate_id)); $certs=array(); // fill an array of fqdn/altnames while ($db->next_record()) { if (!$db->Record["sslcrt"]) continue; // skip NOT FINALIZED certificates !! $certs[]=array("id"=>$db->Record["id"],"fqdn"=>$db->Record["fqdn"]); $altnames=explode("\n",$db->Record["altnames"]); foreach($altnames as $altname) { $certs[]=array("id"=>$db->Record["id"],"fqdn"=>$altname); } } // get the list of subdomains-id that match the following FQDN (or wildcard) $updateids=array(); foreach($certs as $cert) { $subids=$this->searchSubDomain($cert["fqdn"]); foreach($subids as $subid) { $updateids[$subid]=$cert["id"]; } // if this fqdn match a special domain, update its certificate (and mark service for reloading) $this->update_specials_match($cert["id"],$cert["fqdn"]); } // update those subdomains $dom->lock(); foreach($updateids as $id => $certid) { $db->query("UPDATE sub_domaines SET web_action=? WHERE id=?;",array("UPDATE",$id)); $msg->raise("INFO","ssl",sprintf(_("Reloading domain %s as we have new certificate %s"),$id,$certid)); } $dom->unlock(); $this->last_certificate_id=$maxid; variable_set('last_certificate_id',$this->last_certificate_id); } } function fqdnmatch($cert,$fqdn) { if ($cert==$fqdn) return true; if (substr($cert,0,2)=="*." && substr($cert,2)==substr($fqdn,strpos($fqdn,".")+1) ) return true; return false; } // ----------------------------------------------------------------- /** * update special system certificate that matches the cert fqdn: */ function update_specials_match($id,$fqdn) { global $L_FQDN; if ($this->fqdnmatch($fqdn,$L_FQDN)) { // new certificate for the panel $this->copycert("alternc-panel",$id); exec("service apache2 reload"); } $variables=array("fqdn_dovecot","fqdn_postfix","fqdn_proftpd","fqdn_mailman"); foreach($variables as $var) { $value = variable_get($var,null); if ($value) { if ($this->fqdnmatch($fqdn,$value)) { $this->copycert("alternc-".substr($var,5),$id); exec("service ".substr($var,5)." reload"); } } } } // ----------------------------------------------------------------- /** * copy a certificate (by its ID) to the system files * set the correct permissions * try to minimize zero-file-size risk or timing attack */ private function copycert($target,$id) { global $db,$msg; $msg->raise("INFO","ssl",_("Copying system certificate $id on $target")); $db->query("SELECT * FROM certificates WHERE id=?",array($id)); if (!$db->next_record()) return false; if (!file_put_contents("/etc/ssl/certs/".$target.".pem.tmp",trim($db->Record["sslcrt"])."\n".trim($db->Record["sslchain"]))) { $msg->raise("ERROR","ssl",_("Can't put file into /etc/ssl/certs/".$target.".pem.tmp, failing properly")); return false; } chown("/etc/ssl/certs/".$target.".pem.tmp","root"); chgrp("/etc/ssl/certs/".$target.".pem.tmp","ssl-cert"); chmod("/etc/ssl/certs/".$target.".pem.tmp",0755); if (!file_put_contents("/etc/ssl/private/".$target.".key.tmp",$db->Record["sslkey"])) { $msg->raise("ERROR","ssl",_("Can't put file into /etc/ssl/private/".$target.".key.tmp, failing properly")); @unlink("/etc/ssl/certs/".$target.".pem.tmp"); return false; } chown("/etc/ssl/private/".$target.".key.tmp","root"); chgrp("/etc/ssl/private/".$target.".key.tmp","ssl-cert"); chmod("/etc/ssl/private/".$target.".key.tmp",0750); rename("/etc/ssl/certs/".$target.".pem.tmp","/etc/ssl/certs/".$target.".pem"); rename("/etc/ssl/private/".$target.".key.tmp","/etc/ssl/private/".$target.".key"); return true; } // ----------------------------------------------------------------- /** * search for a FQDN as a fqdn or a wildcard in all subdomains currently hosted * return a list of subdomain-id */ function searchSubDomain($fqdn) { global $db; $db->query("SELECT sd.id FROM sub_domaines sd, domaines_type dt WHERE dt.name=sd.type AND dt.only_dns=0 AND (CONCAT(sd.sub,IF(sd.sub!='','.',''),sd.domaine)=? OR CONCAT('*.',SUBSTRING(CONCAT(sd.sub,IF(sd.sub!='','.',''),sd.domaine), INSTR(CONCAT(sd.sub,IF(sd.sub!='','.',''),sd.domaine),'.')+1))=? );", array($fqdn,$fqdn)); $ids=array(); while ($db->next_record()) { $ids[]=$db->Record["id"]; } return $ids; } // ----------------------------------------------------------------- /** * delete old certificates (expired for more than a year) */ function delete_old_certificates() { global $db; $db->query("SELECT c.id,sd.id AS used FROM certificates c LEFT JOIN sub_domaines sd ON sd.certificate_id=c.id WHERE c.status=".self::STATUS_EXPIRED." AND c.validendnext_record()) { if ($db->Record["used"]) { continue; // this certificate is used (even though it's expired :/ ) } $CRTDIR = self::KEY_REPOSITORY . "/" . floor($db->Record["id"]/1000); @unlink($CRTDIR."/".$db->Record["id"].".pem"); @unlink($CRTDIR."/".$db->Record["id"].".key"); @unlink($CRTDIR."/".$db->Record["id"].".chain"); $d=opendir($CRTDIR); $empty=true; while (($c=readdir($d))!==false) { if (is_file($CRTDIR."/".$c)) { $empty=false; break; } } closedir($d); if ($empty) { rmdir($CRTDIR); } } } // ----------------------------------------------------------------- /** Return all the SSL certificates for an account (or the searched one) * @param $filter an integer telling which certificate we want to see (see FILTER_* constants above) * the default is showing all certificate, but only Pending and OK certificates, not expired * when there is more than 10. * @return array all the ssl certificate this user can use * (each array is the content of the certificates table) */ function get_list(&$filter = null) { global $db, $msg, $cuid; $msg->log("ssl", "get_list"); $this->expire_certificates(); $r = array(); // If we have no filter, we filter by default on pending and ok certificates if there is more than 10 of them for the same user. if (is_null($filter)) { $filter = (self::FILTER_PENDING | self::FILTER_OK); } // filter the filter values :) $filter = ($filter & (self::FILTER_PENDING | self::FILTER_OK | self::FILTER_EXPIRED)); // Here filter can't be null (and will be returned to the caller !) $sql = ""; $sql = " uid='$cuid' "; $sql.=" AND status IN (-1"; if ($filter & self::FILTER_PENDING) { $sql.="," . self::STATUS_PENDING; } if ($filter & self::FILTER_OK) { $sql.="," . self::STATUS_OK; } if ($filter & self::FILTER_EXPIRED) { $sql.="," . self::STATUS_EXPIRED; } $sql.=") "; $db->query("SELECT *, UNIX_TIMESTAMP(validstart) AS validstartts, UNIX_TIMESTAMP(validend) AS validendts FROM certificates WHERE $sql ORDER BY validstart DESC;"); if ($db->num_rows()) { while ($db->next_record()) { $r[] = $db->Record; } return $r; } else { $msg->raise("INFO", "ssl", _("No SSL certificates available")); return array(); } } // ----------------------------------------------------------------- /** Generate a new CSR, a new Private RSA Key, for FQDN. * @param $fqdn string the FQDN of the domain name for which we want a CSR. * a wildcard certificate must start by *. * @param $provider string a provider if necessary * @return integer the Certificate ID created in the MySQL database * or false if an error occurred */ function new_csr($fqdn, $provider="manual") { global $db, $msg, $cuid; $msg->log("ssl", "new_csr"); if (substr($fqdn, 0, 2) == "*.") { $f = substr($fqdn, 2); } else { $f = $fqdn; } if (checkfqdn($f)) { $msg->raise("ERROR","ssl", _("Bad FQDN domain name")); return false; } putenv("OPENSSL_CONF=/etc/alternc/openssl.cnf"); $pkey = openssl_pkey_new(); if (!$pkey) { $msg->raise("ERROR","ssl", _("Can't generate a private key (1)")); return false; } $privKey = ""; if (!openssl_pkey_export($pkey, $privKey)) { $msg->raise("ERROR","ssl", _("Can't generate a private key (2)")); return false; } $dn = array("commonName" => $fqdn); // override the (not taken from openssl.cnf) digest to use SHA-2 / SHA256 and not SHA-1 or MD5 : $config = array("digest_alg" => "sha256"); $csr = openssl_csr_new($dn, $pkey, $config); $csrout = ""; openssl_csr_export($csr, $csrout); $db->query("INSERT INTO certificates SET uid=?, status=?, fqdn=?, altnames='', validstart=NOW(), sslcsr=?, sslkey=?, provider=?;",array($cuid, self::STATUS_PENDING, $fqdn, $csrout, $privKey, $provider)); if (!($id = $db->lastid())) { $msg->raise("ERROR","ssl", _("Can't generate a CSR")); return false; } return $id; } // ----------------------------------------------------------------- /** Return all informations of a given certificate for the current user. * @param $id integer the certificate by id * @param $anyuser integer if you want to search cert for any user, set this to true * @return array all the informations of the current certificate as a hash. */ function get_certificate($id, $anyuser=false) { global $db, $msg, $cuid; $msg->log("ssl", "get_certificate"); $id = intval($id); $sql=""; if (!$anyuser) { $sql=" AND uid='".intval($cuid)."' "; } $db->query("SELECT *, UNIX_TIMESTAMP(validstart) AS validstartts, UNIX_TIMESTAMP(validend) AS validendts FROM certificates WHERE id=? $sql;",array($id)); if (!$db->next_record()) { $msg->raise("ERROR","ssl", _("Can't find this Certificate")); return false; } return $db->Record; } // ----------------------------------------------------------------- /** Return all the valid certificates that can be used for a specific FQDN * return the list of certificates by order of preference * (the 2 last will be the default FQDN and the snakeoil if necessary) * keys: id, provider, crt, chain, key, validstart, validend */ function get_valid_certs($fqdn, $provider="") { global $db, $msg, $cuid; $this->expire_certificates(); $db->query("SELECT *, UNIX_TIMESTAMP(validstart) AS validstartts, UNIX_TIMESTAMP(validend) AS validendts FROM certificates WHERE status=".self::STATUS_OK." ORDER BY validstart DESC;"); $good=array(); // list of good certificates $ugly=array(); // good but not with the right provider $bad=array(); // our snakeoil $wildcard="*".substr($fqdn,strpos($fqdn,".")); $defaultwild="*".substr($this->default_certificate_fqdn,strpos($this->default_certificate_fqdn,".")); while($db->next_record()) { $found=false; if ($db->Record["fqdn"]==$fqdn || $db->Record["fqdn"]==$wildcard) { $found=true; } else { $alts=explode("\n",$db->Record["altnames"]); foreach($alts as $alt) { if ($alt==$fqdn || $alt==$wildcard) { $found=true; break; } } } if ($found) { if ($provider=="" || $provider==$db->Record["provider"]) { $good[]=$db->Record; } else { $ugly[]=$db->Record; } } // search for the default one, the one used by the panel if (!count($bad)) { $found=false; if ($db->Record["fqdn"]==$this->default_certificate_fqdn || $db->Record["fqdn"]==$defaultwild) { $found=true; } else { $alts=explode("\n",$db->Record["altnames"]); foreach($alts as $alt) { if ($alt==$this->default_certificate_fqdn || $alt==$defaultwild) { $found=true; break; } } } if ($found) { $bad=$db->Record; } } } // add the one with the bad provider if (count($ugly)) { $good=array_merge($good,$ugly); } // add the panel/default one if (count($bad)) { $good[]=$bad; } // Add the Snakeoil : #0 $db->query("SELECT * FROM certificates WHERE id=0;"); if ($db->next_record()) { $good[]=$db->Record; } return $good; } // ----------------------------------------------------------------- /** Import an existing ssl Key, Certificate and (maybe) a Chained Cert * @param $key string the X.509 PEM-encoded RSA key * @param $crt string the X.509 PEM-encoded certificate, which *must* * be the one signing the private RSA key in $key (we will check that anyway...) * @param $chain string the X.509 PEM-encoded list of SSL Certificate chain if intermediate authorities * TODO: check that the chain is effectively a chain to the CRT ... * @param $provider string the ssl cert provider * @return integer the ID of the newly created certificate in the table * or false if an error occurred */ function import_cert($key, $crt, $chain = "", $provider = "") { global $cuid, $msg, $db; $msg->log("ssl", "import_cert"); // Search for an existing cert: (first) $db->query("SELECT id FROM certificates WHERE sslcrt=?;",array($crt)); if ($db->next_record()) { $msg->raise("ERROR","ssl", _("Certificate already exists in database")); return false; } $result = $this->check_cert($crt, $chain, $key); if ($result === false) { $msg->raise("ERROR","ssl", $this->error); return false; } list($crt, $chain, $key, $crtdata) = $result; $validstart = $crtdata['validFrom_time_t']; $validend = $crtdata['validTo_time_t']; $fqdn = $crtdata["subject"]["CN"]; $altnames = $this->parseAltNames($crtdata["extensions"]["subjectAltName"]); // Everything is PERFECT and has been thoroughly checked, let's insert those in the DB ! $db->query( "INSERT INTO certificates SET uid=?, status=?, fqdn=?, altnames=?, validstart=FROM_UNIXTIME(?), validend=FROM_UNIXTIME(?), sslkey=?, sslcrt=?, sslchain=?, provider=?;", array($cuid, self::STATUS_OK, $fqdn, $altnames, intval($validstart), intval($validend), $key, $crt, $chain, $provider) ); if (!($id = $db->lastid())) { $msg->raise("ERROR","ssl", _("Can't save the Key/Crt/Chain now. Please try later.")); return false; } return $id; } // ----------------------------------------------------------------- /** Import an ssl certificate into an existing certificate entry in the DB. * (finalize an enrollment process) * @param $certid integer the ID in the database of the SSL Certificate * @param $crt string the X.509 PEM-encoded certificate, which *must* * be the one signing the private RSA key in certificate $certid * @param $chain string the X.509 PEM-encoded list of SSL Certificate chain if intermediate authorities * @return integer the ID of the updated certificate in the table * or false if an error occurred */ function finalize($certid, $crt, $chain) { global $cuid, $msg, $db; $msg->log("ssl", "finalize"); $certid = intval($certid); $result = $this->check_cert($crt, $chain, "", $certid); if ($result === false) { $msg->raise("ERROR","ssl", $this->error); return false; } list($crt, $chain, $key, $crtdata) = $result; $validstart = $crtdata['validFrom_time_t']; $validend = $crtdata['validTo_time_t']; $fqdn = $crtdata["subject"]["CN"]; $altnames = $this->parseAltNames($crtdata["extensions"]["subjectAltName"]); // Everything is PERFECT and has been thoroughly checked, let's insert those in the DB ! if (!$db->query( "INSERT INTO certificates (status,fqdn,altnames,validstart,validend,sslcrt,sslchain,sslcsr) SELECT ?,?,?, FROM_UNIXTIME(?), FROM_UNIXTIME(?), ?, ?, sslcsr FROM certificate WHERE id=?;", array(self::STATUS_OK, $fqdn, $altnames, $validstart, $validend, $crt, $chain, $certid) )) { $msg->raise("ERROR","ssl", _("Can't save the Crt/Chain now. Please try later.")); return false; } $newid=$db->lastid(); $db->query("DELETE FROM certificates WHERE id=?;",array($certid)); return $newid; } // ----------------------------------------------------------------- /** Function called by a hook when an AlternC member is deleted. * @access private * TODO: delete unused ssl certificates ?? > do this in the crontab. */ function alternc_del_member() { global $db, $msg, $cuid; $msg->log("ssl", "alternc_del_member"); $db->query("UPDATE certificates SET uid=2000 WHERE uid=?;",array($cuid)); return true; } // ----------------------------------------------------------------- /** Launched by hosting_functions.sh launched by update_domaines.sh * Action may be create/postinst/delete/enable/disable * Change the template for this domain name to have the proper CERTIFICATE * An algorithm determine the best possible certificate, which may be a BAD one * (like a generic self-signed for localhost as a last chance) */ public function updateDomain($action, $type, $fqdn, $mail = 0, $value = "") { global $db, $msg, $dom; $msg->log("ssl", "update_domain($action,$type,$fqdn)"); // the domain type must be a "dns_only=false" one: if (!($domtype=$dom->domains_type_get($type)) || $domtype["only_dns"]==true) { return; // nothing to do : this domain type does not involve Vhosts } if ($action == "postinst") { $msg->log("ssl", "update_domain:CREATE($action,$type,$fqdn)"); $offset = 0; $found = false; do { // try each subdomain (strtok-style) and search them in sub_domaines table: $db->query( "SELECT * FROM sub_domaines WHERE sub=? AND domaine=? AND web_action NOT IN ('','OK') AND type=?", array(substr($fqdn, 0, $offset), substr($fqdn, $offset + ($offset != 0)), $type) ); if ($db->next_record()) { $found = true; break; } $offset = strpos($fqdn, ".", $offset+1); //No more dot, we prevent an infinite loop if (!$offset) { break; } } while (true); if (!$found) { echo "FATAL: didn't found fqdn $fqdn in sub_domaines table !\n"; return; } // found and $db point to it: $subdom = $db->Record; $TARGET_FILE = "/var/lib/alternc/apache-vhost/" . substr($subdom["compte"], -1) . "/" . $subdom["compte"] . "/" . $fqdn . ".conf"; $cert = $this->searchBestCert($subdom,$fqdn); // $cert[crt/key/chain] are path to the proper files // edit apache conf file to set the certificate: $s = file_get_contents($TARGET_FILE); $s = str_replace("%%CRT%%", $cert["crt"], $s); $s = str_replace("%%KEY%%", $cert["key"], $s); if (isset($cert["sslchain"]) && $cert["sslchain"]) { $s = str_replace("%%CHAINLINE%%", "SSLCertificateChainFile " . $cert["chain"], $s); } else { $s = str_replace("%%CHAINLINE%%", "", $s); } file_put_contents($TARGET_FILE, $s); // Edit certif_hosts: $db->query("UPDATE sub_domaines SET certificate_id=? WHERE id=?;",array($cert["id"], $subdom["id"])); } // action==create } // ---------------------------------------------------------------- /** Search for the best certificate for a user and a fqdn * Return a hash with crt, key and maybe chain. * they are the full path to the best certificate for this FQDN. * if necessary, use "default_certificate_fqdn" or a "snakeoil" * @param $subdom array the subdomain entry from sub_domaines table * @param $fqdn string the fully qualified domain name to search for * @return array an has with crt key chain */ public function searchBestCert($subdom,$fqdn) { global $db; // get the first good certificate: list($cert) = $this->get_valid_certs($fqdn, $subdom["provider"]); // we split the certificates by 1000 $CRTDIR = self::KEY_REPOSITORY . "/" . floor($cert["id"]/1000); @mkdir($CRTDIR,0750,true); // set the proper permissions on the Key Repository folder and children : chown(self::KEY_REPOSITORY,"root"); chgrp(self::KEY_REPOSITORY,"ssl-cert"); chmod(self::KEY_REPOSITORY,0750); chown($CRTDIR,"root"); chgrp($CRTDIR,"ssl-cert"); chmod($CRTDIR,0750); if ( !file_exists($CRTDIR . "/" . $cert["id"].".pem") || !file_exists($CRTDIR . "/" . $cert["id"].".key")) { // write the files (first time we use a certificate) file_put_contents($CRTDIR . "/" . $cert["id"].".pem", $cert["sslcrt"]); file_put_contents($CRTDIR . "/" . $cert["id"].".key", $cert["sslkey"]); // set the proper rights on those files : chown($CRTDIR . "/" . $cert["id"].".pem","root"); chgrp($CRTDIR . "/" . $cert["id"].".pem","ssl-cert"); chmod($CRTDIR . "/" . $cert["id"].".pem",0640); chown($CRTDIR . "/" . $cert["id"].".key","root"); chgrp($CRTDIR . "/" . $cert["id"].".key","ssl-cert"); chmod($CRTDIR . "/" . $cert["id"].".key",0640); if (isset($cert["sslchain"]) && $cert["sslchain"]) { file_put_contents($CRTDIR . "/" . $cert["id"] . ".chain", $cert["sslchain"]); chown($CRTDIR . "/" . $cert["id"].".chain","root"); chgrp($CRTDIR . "/" . $cert["id"].".chain","ssl-cert"); chmod($CRTDIR . "/" . $cert["id"].".chain",0640); } } // we have the files, let's fill the output array : $output=array( "id" => $cert["id"], "crt" => $CRTDIR . "/" . $cert["id"].".pem", "key" => $CRTDIR . "/" . $cert["id"].".key", ); if (file_exists($CRTDIR . "/" . $cert["id"].".chain")) { $output["chain"] = $CRTDIR . "/" . $cert["id"].".chain"; } return $output; } // ----------------------------------------------------------------- /** Export every information for an AlternC's account * @access private * EXPERIMENTAL 'sid' function ;) */ function alternc_export_conf() { global $db, $msg, $cuid; $msg->log("ssl", "export"); $str = " "; $db->query("SELECT COUNT(*) AS cnt FROM certificates WHERE uid='$cuid' AND status!=" . self::STATUS_EXPIRED); while ($db->next_record()) { $str.=" " . ($db->Record["id"]) . "\n"; $str.=" " . ($db->Record["sslcsr"]) . "\n"; $str.=" " . ($db->Record["sslkey"]) . "\n"; $str.=" " . ($db->Record["sslcrt"]) . "\n"; $str.=" " . ($db->Record["sslchain"]) . "\n"; } $str.=" \n"; return $str; } // ----------------------------------------------------------------- /** Returns the list of alternate names of an X.509 SSL Certificate * from the attribute list. * @param $str string the $crtdata["extensions"]["subjectAltName"] from openssl * @return array an array of FQDNs */ function parseAltNames($str) { $mat = array(); if (preg_match_all("#DNS:([^,]*)#", $str, $mat, PREG_PATTERN_ORDER)) { return implode("\n", $mat[1]); } else { return ""; } } // ----------------------------------------------------------------- /** Check that a crt is a proper certificate * @param $crt string an SSL Certificate * @param $chain string is a list of certificates * @param $key string is a rsa key associated with certificate * @param $certid if no key is specified, use it from this certificate ID in the table * @return array the crt, chain, key, crtdata(array) after a proper reformatting * or false if an error occurred (in that case $this->error is filled) */ function check_cert($crt, $chain, $key = "", $certid = null) { global $db; // Check that the key crt and chain are really SSL certificates and keys $crt = trim(str_replace("\r\n", "\n", $crt)) . "\n"; $key = trim(str_replace("\r\n", "\n", $key)) . "\n"; $chain = trim(str_replace("\r\n", "\n", $chain)) . "\n"; $this->error = ""; if (trim($key) == "" && !is_null($certid)) { // find it in the DB : $db->query("SELECT sslkey FROM certificates WHERE id=?;",array(intval($certid))); if (!$db->next_record()) { $this->error.=_("Can't find the private key in the certificate table, please check your form."); return false; } $key = $db->f("sslkey"); $key = trim(str_replace("\r\n", "\n", $key)) . "\n"; } if (substr($crt, 0, 28) != "-----BEGIN CERTIFICATE-----\n" || substr($crt, -26, 26) != "-----END CERTIFICATE-----\n") { $this->error.=_("The certificate must begin by BEGIN CERTIFICATE and end by END CERTIFICATE lines. Please check you pasted it in PEM form.") . "
\n"; } if (trim($chain) && (substr($chain, 0, 28) != "-----BEGIN CERTIFICATE-----\n" || substr($chain, -26, 26) != "-----END CERTIFICATE-----\n")) { $this->error.=_("The chained certificate must begin by BEGIN CERTIFICATE and end by END CERTIFICATE lines. Please check you pasted it in PEM form.") . "
\n"; } if ((substr($key, 0, 32) != "-----BEGIN RSA PRIVATE KEY-----\n" || substr($key, -30, 30) != "-----END RSA PRIVATE KEY-----\n") && (substr($key, 0, 28) != "-----BEGIN PRIVATE KEY-----\n" || substr($key, -26, 26) != "-----END PRIVATE KEY-----\n")) { $this->error.=_("The private key must begin by BEGIN (RSA )PRIVATE KEY and end by END (RSA )PRIVATE KEY lines. Please check you pasted it in PEM form.") . "
\n"; } if ($this->error) { return false; } // We split the chained certificates in individuals certificates : $chains = array(); $status = 0; $new = ""; $lines = explode("\n", $chain); foreach ($lines as $line) { if ($line == "-----BEGIN CERTIFICATE-----" && $status == 0) { $status = 1; $new = $line . "\n"; continue; } if ($line == "-----END CERTIFICATE-----" && $status == 1) { $status = 0; $new.=$line . "\n"; $chains[] = $new; $new = ""; continue; } if ($status == 1) { $new.=$line . "\n"; } } // here chains contains all the ssl certificates in the chained certs. // Now we check those using Openssl functions (real check :) ) $rchains = array(); $i = 0; foreach ($chains as $tmpcert) { $i++; $tmpr = openssl_x509_read($tmpcert); if ($tmpr === false) { $this->error.=sprintf(_("The %d-th certificate in the chain is invalid"), $i) . "
\n"; } else { $rchains[] = $tmpr; } } $rcrt = openssl_x509_read($crt); $crtdata = openssl_x509_parse($crt); if ($rcrt === false || $crtdata === false) { $this->error.=_("The certificate is invalid.") . "
\n"; } $rkey = openssl_pkey_get_private($key); if ($rkey === false) { $this->error.=_("The private key is invalid.") . "
\n"; } if (!$this->error) { // check that the private key and the certificates are matching : if (!openssl_x509_check_private_key($rcrt, $rkey)) { $this->error.=_("The private key is not the one signed inside the certificate.") . "
\n"; } } if (!$this->error) { // Everything is fine, let's recreate crt, chain, key from our internal OpenSSL structures: if (!openssl_x509_export($rcrt, $crt)) { $this->error.=_("Can't export your certificate as a string, please check its syntax.") . "
\n"; } $chain = ""; foreach ($rchains as $r) { if (!openssl_x509_export($r, $tmp)) { $this->error.=_("Can't export one of your chained certificates as a string, please check its syntax.") . "
\n"; } else { $chain.=$tmp; } } if (!openssl_pkey_export($rkey, $key)) { $this->error.=_("Can't export your private key as a string, please check its syntax.") . "
\n"; } } return array($crt, $chain, $key, $crtdata); } } /* Class m_ssl */