Alternc  latest
Alternc logiel libre pour l'hébergement
m_ssl Class Reference

SSL Certificates management class. More...

Public Member Functions

 m_ssl ()
 Constructor. More...
 
 get_fqdn_specials ()
 Return the list of special FQDN for which we'd like to obtain a certificate too. More...
 
 expire_certificates ()
 set expired certificates as such : More...
 
 cron_new_certs ()
 Crontab launched every minute to search for new certificates and launch web_action="UPDATE". More...
 
 fqdnmatch ($cert, $fqdn)
 
 update_specials_match ($id, $fqdn)
 update special system certificate that matches the cert fqdn: More...
 
 searchSubDomain ($fqdn)
 search for a FQDN as a fqdn or a wildcard in all subdomains currently hosted return a list of subdomain-id More...
 
 delete_old_certificates ()
 delete old certificates (expired for more than a year) More...
 
 get_list (&$filter=null)
 Return all the SSL certificates for an account (or the searched one) More...
 
 new_csr ($fqdn, $provider="manual")
 Generate a new CSR, a new Private RSA Key, for FQDN. More...
 
 get_certificate ($id, $anyuser=false)
 Return all informations of a given certificate for the current user. More...
 
 get_certificate_path ($id)
 Return paths to certificate, key, and chain for a certificate given it's ID. More...
 
 get_valid_certs ($fqdn, $provider="")
 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. More...
 
 import_cert ($key, $crt, $chain="", $provider="")
 Import an existing ssl Key, Certificate and (maybe) a Chained Cert. More...
 
 finalize ($certid, $crt, $chain)
 Import an ssl certificate into an existing certificate entry in the DB. More...
 
 alternc_del_member ()
 Function called by a hook when an AlternC member is deleted. More...
 
 updateDomain ($action, $type, $fqdn, $mail=0, $value="")
 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) More...
 
 hook_updatedomains_web_before ($subdomid)
 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) More...
 
 searchBestCert ($subdom, $fqdn)
 Search for the best certificate for a user and a fqdn Return a hash with crt, key and maybe chain. More...
 
 write_cert_file ($cert)
 Write certificate file into KEY_REPOSITORY. More...
 
 alternc_export_conf ()
 Export every information for an AlternC's account @access private EXPERIMENTAL 'sid' function ;) More...
 
 parseAltNames ($str)
 Returns the list of alternate names of an X.509 SSL Certificate from the attribute list. More...
 
 check_cert ($crt, $chain, $key="", $certid=null)
 Check that a crt is a proper certificate. More...
 

Public Attributes

const STATUS_PENDING = 0
 
const STATUS_OK = 1
 
const STATUS_EXPIRED = 99
 
 $error = ""
 
const FILTER_PENDING = 1
 
const FILTER_OK = 2
 
const FILTER_EXPIRED = 4
 
const KEY_REPOSITORY = "/var/lib/alternc/ssl/private"
 
const SPECIAL_CERTIFICATE_ID_PATH = "/var/lib/alternc/ssl/special_id.json"
 

Private Member Functions

 copycert ($target, $id)
 copy a certificate (by its ID) to the system files set the correct permissions try to minimize zero-file-size risk or timing attack More...
 

Detailed Description

SSL Certificates management class.

Definition at line 31 of file m_ssl.php.

Member Function Documentation

◆ alternc_del_member()

m_ssl::alternc_del_member ( )

Function called by a hook when an AlternC member is deleted.

@access private TODO: delete unused ssl certificates ?? > do this in the crontab.

Definition at line 575 of file m_ssl.php.

575  {
576  global $db, $msg, $cuid;
577  $msg->log("ssl", "alternc_del_member");
578  $db->query("UPDATE certificates SET uid=2000 WHERE uid=?;",array($cuid));
579  return true;
580  }

◆ alternc_export_conf()

m_ssl::alternc_export_conf ( )

Export every information for an AlternC's account @access private EXPERIMENTAL 'sid' function ;)

Definition at line 753 of file m_ssl.php.

753  {
754  global $db, $msg, $cuid;
755  $msg->log("ssl", "export");
756  $str = " <ssl>";
757  $db->query("SELECT COUNT(*) AS cnt FROM certificates WHERE uid='$cuid' AND status!=" . self::STATUS_EXPIRED);
758  while ($db->next_record()) {
759  $str.=" <id>" . ($db->Record["id"]) . "</id>\n";
760  $str.=" <csr>" . ($db->Record["sslcsr"]) . "</key>\n";
761  $str.=" <key>" . ($db->Record["sslkey"]) . "<key>\n";
762  $str.=" <crt>" . ($db->Record["sslcrt"]) . "</crt>\n";
763  $str.=" <chain>" . ($db->Record["sslchain"]) . "<chain>\n";
764  }
765  $str.=" </ssl>\n";
766  return $str;
767  }

◆ check_cert()

m_ssl::check_cert (   $crt,
  $chain,
  $key = "",
  $certid = null 
)

Check that a crt is a proper certificate.

Parameters
$crtstring an SSL Certificate
$chainstring is a list of certificates
$keystring is a rsa key associated with certificate
$certidif no key is specified, use it from this certificate ID in the table
Returns
array the crt, chain, key, crtdata(array) after a proper reformatting
or false if an error occurred (in that case $this->error is filled)

Definition at line 794 of file m_ssl.php.

794  {
795  global $db;
796  // Check that the key crt and chain are really SSL certificates and keys
797  $crt = trim(str_replace("\r\n", "\n", $crt)) . "\n";
798  $key = trim(str_replace("\r\n", "\n", $key)) . "\n";
799  $chain = trim(str_replace("\r\n", "\n", $chain)) . "\n";
800 
801  $this->error = "";
802  if (trim($key) == "" && !is_null($certid)) {
803  // find it in the DB :
804  $db->query("SELECT sslkey FROM certificates WHERE id=?;",array(intval($certid)));
805  if (!$db->next_record()) {
806  $this->error.=_("Can't find the private key in the certificate table, please check your form.");
807  return false;
808  }
809  $key = $db->f("sslkey");
810  $key = trim(str_replace("\r\n", "\n", $key)) . "\n";
811  }
812 
813  if (substr($crt, 0, 28) != "-----BEGIN CERTIFICATE-----\n" ||
814  substr($crt, -26, 26) != "-----END CERTIFICATE-----\n") {
815  $this->error.=_("The certificate must begin by BEGIN CERTIFICATE and end by END CERTIFICATE lines. Please check you pasted it in PEM form.") . "<br>\n";
816  }
817  if (trim($chain) &&
818  (substr($chain, 0, 28) != "-----BEGIN CERTIFICATE-----\n" ||
819  substr($chain, -26, 26) != "-----END CERTIFICATE-----\n")) {
820  $this->error.=_("The chained certificate must begin by BEGIN CERTIFICATE and end by END CERTIFICATE lines. Please check you pasted it in PEM form.") . "<br>\n";
821  }
822  if ((substr($key, 0, 32) != "-----BEGIN RSA PRIVATE KEY-----\n" ||
823  substr($key, -30, 30) != "-----END RSA PRIVATE KEY-----\n") &&
824  (substr($key, 0, 28) != "-----BEGIN PRIVATE KEY-----\n" ||
825  substr($key, -26, 26) != "-----END PRIVATE KEY-----\n")) {
826  $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.") . "<br>\n";
827  }
828  if ($this->error) {
829  return false;
830  }
831 
832  // We split the chained certificates in individuals certificates :
833  $chains = array();
834  $status = 0;
835  $new = "";
836  $lines = explode("\n", $chain);
837  foreach ($lines as $line) {
838  if ($line == "-----BEGIN CERTIFICATE-----" && $status == 0) {
839  $status = 1;
840  $new = $line . "\n";
841  continue;
842  }
843  if ($line == "-----END CERTIFICATE-----" && $status == 1) {
844  $status = 0;
845  $new.=$line . "\n";
846  $chains[] = $new;
847  $new = "";
848  continue;
849  }
850  if ($status == 1) {
851  $new.=$line . "\n";
852  }
853  }
854  // here chains contains all the ssl certificates in the chained certs.
855  // Now we check those using Openssl functions (real check :) )
856  $rchains = array();
857  $i = 0;
858  foreach ($chains as $tmpcert) {
859  $i++;
860  $tmpr = openssl_x509_read($tmpcert);
861  if ($tmpr === false) {
862  $this->error.=sprintf(_("The %d-th certificate in the chain is invalid"), $i) . "<br>\n";
863  } else {
864  $rchains[] = $tmpr;
865  }
866  }
867  $rcrt = openssl_x509_read($crt);
868  $crtdata = openssl_x509_parse($crt);
869  if ($rcrt === false || $crtdata === false) {
870  $this->error.=_("The certificate is invalid.") . "<br>\n";
871  }
872 
873  $rkey = openssl_pkey_get_private($key);
874  if ($rkey === false) {
875  $this->error.=_("The private key is invalid.") . "<br>\n";
876  }
877  if (!$this->error) {
878  // check that the private key and the certificates are matching :
879  if (!openssl_x509_check_private_key($rcrt, $rkey)) {
880  $this->error.=_("The private key is not the one signed inside the certificate.") . "<br>\n";
881  }
882  }
883  if (!$this->error) {
884  // Everything is fine, let's recreate crt, chain, key from our internal OpenSSL structures:
885  if (!openssl_x509_export($rcrt, $crt)) {
886  $this->error.=_("Can't export your certificate as a string, please check its syntax.") . "<br>\n";
887  }
888  $chain = "";
889  foreach ($rchains as $r) {
890  if (!openssl_x509_export($r, $tmp)) {
891  $this->error.=_("Can't export one of your chained certificates as a string, please check its syntax.") . "<br>\n";
892  } else {
893  $chain.=$tmp;
894  }
895  }
896  if (!openssl_pkey_export($rkey, $key)) {
897  $this->error.=_("Can't export your private key as a string, please check its syntax.") . "<br>\n";
898  }
899  }
900  return array($crt, $chain, $key, $crtdata);
901  }

Referenced by finalize(), and import_cert().

◆ copycert()

m_ssl::copycert (   $target,
  $id 
)
private

copy a certificate (by its ID) to the system files set the correct permissions try to minimize zero-file-size risk or timing attack

Definition at line 181 of file m_ssl.php.

181  {
182  global $db,$msg;
183  $msg->raise("INFO","ssl",_("Copying system certificate $id on $target"));
184  $db->query("SELECT * FROM certificates WHERE id=?",array($id));
185  if (!$db->next_record()) return false;
186  if (!file_put_contents("/etc/ssl/certs/".$target.".pem.tmp",trim($db->Record["sslcrt"])."\n".trim($db->Record["sslchain"]))) {
187  $msg->raise("ERROR","ssl",_("Can't put file into /etc/ssl/certs/".$target.".pem.tmp, failing properly"));
188  return false;
189  }
190  chown("/etc/ssl/certs/".$target.".pem.tmp","root");
191  chgrp("/etc/ssl/certs/".$target.".pem.tmp","ssl-cert");
192  chmod("/etc/ssl/certs/".$target.".pem.tmp",0755);
193  if (!file_put_contents("/etc/ssl/private/".$target.".key.tmp",$db->Record["sslkey"])) {
194  $msg->raise("ERROR","ssl",_("Can't put file into /etc/ssl/private/".$target.".key.tmp, failing properly"));
195  @unlink("/etc/ssl/certs/".$target.".pem.tmp");
196  return false;
197  }
198  chown("/etc/ssl/private/".$target.".key.tmp","root");
199  chgrp("/etc/ssl/private/".$target.".key.tmp","ssl-cert");
200  chmod("/etc/ssl/private/".$target.".key.tmp",0750);
201 
202  rename("/etc/ssl/certs/".$target.".pem.tmp","/etc/ssl/certs/".$target.".pem");
203  rename("/etc/ssl/private/".$target.".key.tmp","/etc/ssl/private/".$target.".key");
204  return true;
205  }

Referenced by update_specials_match().

◆ cron_new_certs()

m_ssl::cron_new_certs ( )

Crontab launched every minute to search for new certificates and launch web_action="UPDATE".

Definition at line 95 of file m_ssl.php.

95  {
96  global $db,$msg,$dom;
97  $db->query("SELECT max(id) AS maxid FROM certificates;");
98  if (!$db->next_record()) {
99  $msg->raise("ERROR","ssl",_("FATAL: no certificates in certificates table, even the SnakeOil one??"));
100  return false;
101  }
102  $maxid=$db->Record["maxid"];
103  if ($maxid>$this->last_certificate_id) {
104  $db->query("SELECT id,fqdn,altnames,sslcrt FROM certificates WHERE id>?",array($this->last_certificate_id));
105  $certs=array();
106  // fill an array of fqdn/altnames
107  while ($db->next_record()) {
108  if (!$db->Record["sslcrt"]) continue; // skip NOT FINALIZED certificates !!
109 
110  $certs[]=array("id"=>$db->Record["id"],"fqdn"=>$db->Record["fqdn"]);
111  $altnames=explode("\n",$db->Record["altnames"]);
112  foreach($altnames as $altname) {
113  $certs[]=array("id"=>$db->Record["id"],"fqdn"=>$altname);
114  }
115  }
116 
117  // get the list of subdomains-id that match the following FQDN (or wildcard)
118  $updateids=array();
119  foreach($certs as $cert) {
120  $subids=$this->searchSubDomain($cert["fqdn"]);
121  foreach($subids as $subid) {
122  $updateids[$subid]=$cert["id"];
123  }
124  // if this fqdn match a special domain, update its certificate (and mark service for reloading)
125  $this->update_specials_match($cert["id"],$cert["fqdn"]);
126  }
127 
128  // update those subdomains
129  $dom->lock();
130  foreach($updateids as $id => $certid) {
131  $db->query("UPDATE sub_domaines SET web_action=? WHERE id=?;",array("UPDATE",$id));
132  $msg->raise("INFO","ssl",sprintf(_("Reloading domain %s as we have new certificate %s"),$id,$certid));
133  }
134  $dom->unlock();
135  $this->last_certificate_id=$maxid;
136  variable_set('last_certificate_id',$this->last_certificate_id);
137  }
138  }
update_specials_match($id, $fqdn)
update special system certificate that matches the cert fqdn:
Definition: m_ssl.php:154
searchSubDomain($fqdn)
search for a FQDN as a fqdn or a wildcard in all subdomains currently hosted return a list of subdoma...
Definition: m_ssl.php:213

References searchSubDomain(), and update_specials_match().

◆ delete_old_certificates()

m_ssl::delete_old_certificates ( )

delete old certificates (expired for more than a year)

Definition at line 233 of file m_ssl.php.

233  {
234  global $db;
235  $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.validend<DATE_SUB(NOW(), INTERVAL 12 MONTH) AND c.validend!='0000-00-00 00:00:00';");
236  while ($db->next_record()) {
237  if ($db->Record["used"]) {
238  continue; // this certificate is used (even though it's expired :/ )
239  }
240  $CRTDIR = self::KEY_REPOSITORY . "/" . floor($db->Record["id"]/1000);
241  @unlink($CRTDIR."/".$db->Record["id"].".pem");
242  @unlink($CRTDIR."/".$db->Record["id"].".key");
243  @unlink($CRTDIR."/".$db->Record["id"].".chain");
244  $d=opendir($CRTDIR);
245  $empty=true;
246  while (($c=readdir($d))!==false) {
247  if (is_file($CRTDIR."/".$c)) {
248  $empty=false;
249  break;
250  }
251  }
252  closedir($d);
253  if ($empty) {
254  rmdir($CRTDIR);
255  }
256  }
257  }
$c
Definition: mem_param.php:46

References $c.

◆ expire_certificates()

m_ssl::expire_certificates ( )

set expired certificates as such :

Definition at line 84 of file m_ssl.php.

84  {
85  global $db;
86  $db->query("UPDATE certificates SET status=".self::STATUS_EXPIRED." WHERE status=".self::STATUS_OK." AND validend<NOW();");
87  }

Referenced by get_list(), and get_valid_certs().

◆ finalize()

m_ssl::finalize (   $certid,
  $crt,
  $chain 
)

Import an ssl certificate into an existing certificate entry in the DB.

(finalize an enrollment process)

Parameters
$certidinteger the ID in the database of the SSL Certificate
$crtstring the X.509 PEM-encoded certificate, which must be the one signing the private RSA key in certificate $certid
$chainstring the X.509 PEM-encoded list of SSL Certificate chain if intermediate authorities
Returns
integer the ID of the updated certificate in the table or false if an error occurred

Definition at line 538 of file m_ssl.php.

538  {
539  global $cuid, $msg, $db;
540  $msg->log("ssl", "finalize");
541 
542  $certid = intval($certid);
543  $result = $this->check_cert($crt, $chain, "", $certid);
544  if ($result === false) {
545  $msg->raise("ERROR","ssl", $this->error);
546  return false;
547  }
548  list($crt, $chain, $key, $crtdata) = $result;
549 
550  $validstart = $crtdata['validFrom_time_t'];
551  $validend = $crtdata['validTo_time_t'];
552  $fqdn = $crtdata["subject"]["CN"];
553  $altnames = $this->parseAltNames($crtdata["extensions"]["subjectAltName"]);
554 
555  // Everything is PERFECT and has been thoroughly checked, let's insert those in the DB !
556  if (!$db->query(
557  "INSERT INTO certificates (status,fqdn,altnames,validstart,validend,sslcrt,sslchain,sslcsr)
558 SELECT ?,?,?, FROM_UNIXTIME(?), FROM_UNIXTIME(?), ?, ?, sslcsr FROM certificate WHERE id=?;",
559  array(self::STATUS_OK, $fqdn, $altnames, $validstart, $validend, $crt, $chain, $certid)
560  )) {
561  $msg->raise("ERROR","ssl", _("Can't save the Crt/Chain now. Please try later."));
562  return false;
563  }
564  $newid=$db->lastid();
565  $db->query("DELETE FROM certificates WHERE id=?;",array($certid));
566  return $newid;
567  }
check_cert($crt, $chain, $key="", $certid=null)
Check that a crt is a proper certificate.
Definition: m_ssl.php:794
parseAltNames($str)
Returns the list of alternate names of an X.509 SSL Certificate from the attribute list.
Definition: m_ssl.php:776

References check_cert(), and parseAltNames().

◆ fqdnmatch()

m_ssl::fqdnmatch (   $cert,
  $fqdn 
)

Definition at line 141 of file m_ssl.php.

141  {
142  if ($cert==$fqdn)
143  return true;
144  if (substr($cert,0,2)=="*." &&
145  substr($cert,2)==substr($fqdn,strpos($fqdn,".")+1) )
146  return true;
147  return false;
148  }

Referenced by update_specials_match().

◆ get_certificate()

m_ssl::get_certificate (   $id,
  $anyuser = false 
)

Return all informations of a given certificate for the current user.

Parameters
$idinteger the certificate by id
$anyuserinteger if you want to search cert for any user, set this to true
Returns
array all the informations of the current certificate as a hash.

Definition at line 359 of file m_ssl.php.

359  {
360  global $db, $msg, $cuid;
361  $msg->log("ssl", "get_certificate");
362  $id = intval($id);
363  $sql="";
364  if (!$anyuser) {
365  $sql=" AND uid='".intval($cuid)."' ";
366  }
367  $db->query("SELECT *, UNIX_TIMESTAMP(validstart) AS validstartts, UNIX_TIMESTAMP(validend) AS validendts FROM certificates WHERE id=? $sql;",array($id));
368  if (!$db->next_record()) {
369  $msg->raise("ERROR","ssl", _("Can't find this Certificate"));
370  return false;
371  }
372  return $db->Record;
373  }

◆ get_certificate_path()

m_ssl::get_certificate_path (   $id)

Return paths to certificate, key, and chain for a certificate given it's ID.

Parameters
$idinteger the certificate by id
Returns
array cert, key, chain (not mandatory) with full path.

Definition at line 382 of file m_ssl.php.

382  {
383  global $db, $msg, $cuid;
384  $msg->log("ssl", "get_certificate_path",$id);
385  $id = intval($id);
386  $db->query("SELECT id FROM certificates WHERE id=?;",array($id));
387  if (!$db->next_record()) {
388  $msg->raise("ERROR","ssl", _("Can't find this Certificate"));
389  // Return cert 0 info :)
390  $id=0;
391  }
392  $chain=self::KEY_REPOSITORY."/".floor($id/1000)."/".$id.".chain";
393  if (!file_exists($chain))
394  $chain=false;
395 
396  return array(
397  "cert" => self::KEY_REPOSITORY."/".floor($id/1000)."/".$id.".pem",
398  "key" => self::KEY_REPOSITORY."/".floor($id/1000)."/".$id.".key",
399  "chain" => $chain
400  );
401  }

◆ get_fqdn_specials()

m_ssl::get_fqdn_specials ( )

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

Definition at line 66 of file m_ssl.php.

66  {
67  global $L_FQDN;
68  $specials=array($L_FQDN);
69  $variables=array("fqdn_dovecot","fqdn_postfix","fqdn_proftpd","fqdn_mailman");
70  foreach($variables as $var) {
71  $value = variable_get($var,null);
72  if ($value && !in_array($value,$specials)) {
73  $specials[]=$value;
74  }
75  }
76  return $specials;
77  }

◆ get_list()

m_ssl::get_list ( $filter = null)

Return all the SSL certificates for an account (or the searched one)

Parameters
$filteran 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.
Returns
array all the ssl certificate this user can use (each array is the content of the certificates table)

Definition at line 268 of file m_ssl.php.

268  {
269  global $db, $msg, $cuid;
270  $msg->log("ssl", "get_list");
271  $this->expire_certificates();
272  $r = array();
273  // 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.
274  if (is_null($filter)) {
275  $filter = (self::FILTER_PENDING | self::FILTER_OK);
276  }
277  // filter the filter values :)
278  $filter = ($filter & (self::FILTER_PENDING | self::FILTER_OK | self::FILTER_EXPIRED));
279  // Here filter can't be null (and will be returned to the caller !)
280  $sql = "";
281  $sql = " uid='$cuid' ";
282  $sql.=" AND status IN (-1";
283  if ($filter & self::FILTER_PENDING) {
284  $sql.="," . self::STATUS_PENDING;
285  }
286  if ($filter & self::FILTER_OK) {
287  $sql.="," . self::STATUS_OK;
288  }
289  if ($filter & self::FILTER_EXPIRED) {
290  $sql.="," . self::STATUS_EXPIRED;
291  }
292  $sql.=") ";
293  $db->query("SELECT *, UNIX_TIMESTAMP(validstart) AS validstartts, UNIX_TIMESTAMP(validend) AS validendts FROM certificates WHERE $sql ORDER BY validstart DESC;");
294  if ($db->num_rows()) {
295  while ($db->next_record()) {
296  $r[] = $db->Record;
297  }
298  return $r;
299  } else {
300  $msg->raise("INFO", "ssl", _("No SSL certificates available"));
301  return array();
302  }
303  }
const STATUS_EXPIRED
Definition: m_ssl.php:35
const STATUS_OK
Definition: m_ssl.php:34
expire_certificates()
set expired certificates as such :
Definition: m_ssl.php:84
const FILTER_OK
Definition: m_ssl.php:42
const STATUS_PENDING
Definition: m_ssl.php:33
const FILTER_EXPIRED
Definition: m_ssl.php:43

References expire_certificates(), FILTER_EXPIRED, FILTER_OK, STATUS_EXPIRED, STATUS_OK, and STATUS_PENDING.

◆ get_valid_certs()

m_ssl::get_valid_certs (   $fqdn,
  $provider = "" 
)

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.

Definition at line 409 of file m_ssl.php.

409  {
410  global $db, $msg, $cuid;
411  $this->expire_certificates();
412 
413  $db->query("SELECT *, UNIX_TIMESTAMP(validstart) AS validstartts, UNIX_TIMESTAMP(validend) AS validendts FROM certificates WHERE status=".self::STATUS_OK." ORDER BY validstart DESC;");
414 
415  $good=array(); // list of good certificates
416  $ugly=array(); // good but not with the right provider
417  $bad=array(); // our snakeoil
418 
419  $wildcard="*".substr($fqdn,strpos($fqdn,"."));
420  $defaultwild="*".substr($this->default_certificate_fqdn,strpos($this->default_certificate_fqdn,"."));
421 
422  while($db->next_record()) {
423  $found=false;
424  if ($db->Record["fqdn"]==$fqdn || $db->Record["fqdn"]==$wildcard) {
425  $found=true;
426 
427  } else {
428  $alts=explode("\n",$db->Record["altnames"]);
429  foreach($alts as $alt) {
430  if ($alt==$fqdn || $alt==$wildcard) {
431  $found=true;
432  break;
433  }
434  }
435  }
436  if ($found) {
437  if ($provider=="" || $provider==$db->Record["provider"]) {
438  $good[]=$db->Record;
439  } else {
440  $ugly[]=$db->Record;
441  }
442  }
443  // search for the default one, the one used by the panel
444  if (!count($bad)) {
445  $found=false;
446  if ($db->Record["fqdn"]==$this->default_certificate_fqdn || $db->Record["fqdn"]==$defaultwild) {
447  $found=true;
448  } else {
449  $alts=explode("\n",$db->Record["altnames"]);
450  foreach($alts as $alt) {
451  if ($alt==$this->default_certificate_fqdn || $alt==$defaultwild) {
452  $found=true;
453  break;
454  }
455  }
456  }
457  if ($found) {
458  $bad=$db->Record;
459  }
460  }
461  }
462  // add the one with the bad provider
463  if (count($ugly)) {
464  $good=array_merge($good,$ugly);
465  }
466  // add the panel/default one
467  if (count($bad)) {
468  $good[]=$bad;
469  }
470  // Add the Snakeoil : #0
471  $db->query("SELECT * FROM certificates WHERE id=0;");
472  if ($db->next_record()) {
473  $good[]=$db->Record;
474  }
475  return $good;
476  }

References expire_certificates().

Referenced by hook_updatedomains_web_before(), and searchBestCert().

◆ hook_updatedomains_web_before()

m_ssl::hook_updatedomains_web_before (   $subdomid)

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)

Definition at line 660 of file m_ssl.php.

660  {
661  global $db, $msg, $dom;
662  $msg->log("ssl", "hook_updatedomains_web_before($subdomid)");
663 
664  $db->query("SELECT sd.*, dt.only_dns, dt.has_https_option, m.login FROM domaines_type dt, sub_domaines sd LEFT JOIN membres m ON m.uid=sd.compte WHERE dt.name=sd.type AND sd.web_action!='OK' AND id=?;",array($subdomid));
665  $db->next_record();
666  $subdom=$db->Record;
667  $domtype=$dom->domains_type_get($subdom["type"]);
668  // the domain type must be a "dns_only=false" one:
669  if ($domtype["only_dns"]==true) {
670  return; // nothing to do : this domain type does not involve Vhosts
671  }
672  $subdom["fqdn"]=$subdom["sub"].(($subdom["sub"])?".":"").$subdom["domaine"];
673 
674  list($cert) = $this->get_valid_certs($subdom["fqdn"], $subdom["provider"]);
675  $this->write_cert_file($cert);
676  // Edit certif_hosts:
677  $db->query("UPDATE sub_domaines SET certificate_id=? WHERE id=?;",array($cert["id"], $subdom["id"]));
678  }
get_valid_certs($fqdn, $provider="")
Return all the valid certificates that can be used for a specific FQDN return the list of certificate...
Definition: m_ssl.php:409
write_cert_file($cert)
Write certificate file into KEY_REPOSITORY.
Definition: m_ssl.php:713

References get_valid_certs(), and write_cert_file().

◆ import_cert()

m_ssl::import_cert (   $key,
  $crt,
  $chain = "",
  $provider = "" 
)

Import an existing ssl Key, Certificate and (maybe) a Chained Cert.

Parameters
$keystring the X.509 PEM-encoded RSA key
$crtstring the X.509 PEM-encoded certificate, which must be the one signing the private RSA key in $key (we will check that anyway...)
$chainstring 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 ...
$providerstring the ssl cert provider
Returns
integer the ID of the newly created certificate in the table or false if an error occurred

Definition at line 490 of file m_ssl.php.

490  {
491  global $cuid, $msg, $db;
492  $msg->log("ssl", "import_cert");
493 
494  // Search for an existing cert: (first)
495  $db->query("SELECT id FROM certificates WHERE sslcrt=?;",array($crt));
496  if ($db->next_record()) {
497  $msg->raise("ERROR","ssl", _("Certificate already exists in database"));
498  return false;
499  }
500 
501  $result = $this->check_cert($crt, $chain, $key);
502  if ($result === false) {
503  $msg->raise("ERROR","ssl", $this->error);
504  return false;
505  }
506  list($crt, $chain, $key, $crtdata) = $result;
507 
508  $validstart = $crtdata['validFrom_time_t'];
509  $validend = $crtdata['validTo_time_t'];
510  $fqdn = $crtdata["subject"]["CN"];
511  $altnames = $this->parseAltNames($crtdata["extensions"]["subjectAltName"]);
512 
513  // Everything is PERFECT and has been thoroughly checked, let's insert those in the DB !
514  // The sslcsr column is required as it has no default value, giving it an empty value.
515  $db->query(
516  "INSERT INTO certificates SET uid=?, status=?, fqdn=?, altnames=?, validstart=FROM_UNIXTIME(?), validend=FROM_UNIXTIME(?), sslkey=?, sslcrt=?, sslchain=?, provider=?, sslcsr = '';",
517  array($cuid, self::STATUS_OK, $fqdn, $altnames, intval($validstart), intval($validend), $key, $crt, $chain, $provider)
518  );
519  if (!($id = $db->lastid())) {
520  $msg->log('ssl', 'impoert_cert', 'insert query failed (' . print_r($db->last_error(), TRUE) . ')');
521  $msg->raise("ERROR","ssl", _("Can't save the Key/Crt/Chain now. Please try later."));
522  return false;
523  }
524  return $id;
525  }

References check_cert(), and parseAltNames().

◆ m_ssl()

m_ssl::m_ssl ( )

Constructor.

Definition at line 52 of file m_ssl.php.

52  {
53  global $L_FQDN;
54  $this->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');
55  $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');
56  }

◆ new_csr()

m_ssl::new_csr (   $fqdn,
  $provider = "manual" 
)

Generate a new CSR, a new Private RSA Key, for FQDN.

Parameters
$fqdnstring the FQDN of the domain name for which we want a CSR. a wildcard certificate must start by *.
$providerstring a provider if necessary
Returns
integer the Certificate ID created in the MySQL database or false if an error occurred

Definition at line 315 of file m_ssl.php.

315  {
316  global $db, $msg, $cuid;
317  $msg->log("ssl", "new_csr");
318  if (substr($fqdn, 0, 2) == "*.") {
319  $f = substr($fqdn, 2);
320  } else {
321  $f = $fqdn;
322  }
323  if (checkfqdn($f)) {
324  $msg->raise("ERROR","ssl", _("Bad FQDN domain name"));
325  return false;
326  }
327  putenv("OPENSSL_CONF=/etc/alternc/openssl.cnf");
328  $pkey = openssl_pkey_new();
329  if (!$pkey) {
330  $msg->raise("ERROR","ssl", _("Can't generate a private key (1)"));
331  return false;
332  }
333  $privKey = "";
334  if (!openssl_pkey_export($pkey, $privKey)) {
335  $msg->raise("ERROR","ssl", _("Can't generate a private key (2)"));
336  return false;
337  }
338  $dn = array("commonName" => $fqdn);
339  // override the (not taken from openssl.cnf) digest to use SHA-2 / SHA256 and not SHA-1 or MD5 :
340  $config = array("digest_alg" => "sha256");
341  $csr = openssl_csr_new($dn, $pkey, $config);
342  $csrout = "";
343  openssl_csr_export($csr, $csrout);
344  $db->query("INSERT INTO certificates SET uid=?, status=?, fqdn=?, altnames='', validstart=NOW(), sslcsr=?, sslkey=?, provider=?;",array($cuid, self::STATUS_PENDING, $fqdn, $csrout, $privKey, $provider));
345  if (!($id = $db->lastid())) {
346  $msg->raise("ERROR","ssl", _("Can't generate a CSR"));
347  return false;
348  }
349  return $id;
350  }

◆ parseAltNames()

m_ssl::parseAltNames (   $str)

Returns the list of alternate names of an X.509 SSL Certificate from the attribute list.

Parameters
$strstring the $crtdata["extensions"]["subjectAltName"] from openssl
Returns
array an array of FQDNs

Definition at line 776 of file m_ssl.php.

776  {
777  $mat = array();
778  if (preg_match_all("#DNS:([^,]*)#", $str, $mat, PREG_PATTERN_ORDER)) {
779  return implode("\n", $mat[1]);
780  } else {
781  return "";
782  }
783  }

Referenced by finalize(), and import_cert().

◆ searchBestCert()

m_ssl::searchBestCert (   $subdom,
  $fqdn 
)

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"

Parameters
$subdomarray the subdomain entry from sub_domaines table
$fqdnstring the fully qualified domain name to search for
Returns
array an has with crt key chain

Definition at line 690 of file m_ssl.php.

690  {
691  global $db;
692 
693  // get the first good certificate:
694  list($cert) = $this->get_valid_certs($fqdn, $subdom["provider"]);
695  $this->write_cert_file($cert);
696  // we have the files, let's fill the output array :
697  $output=array(
698  "id" => $cert["id"],
699  "crt" => $CRTDIR . "/" . $cert["id"].".pem",
700  "key" => $CRTDIR . "/" . $cert["id"].".key",
701  );
702  if (file_exists($CRTDIR . "/" . $cert["id"].".chain")) {
703  $output["chain"] = $CRTDIR . "/" . $cert["id"].".chain";
704  }
705  return $output;
706  }

References get_valid_certs(), and write_cert_file().

Referenced by updateDomain().

◆ searchSubDomain()

m_ssl::searchSubDomain (   $fqdn)

search for a FQDN as a fqdn or a wildcard in all subdomains currently hosted return a list of subdomain-id

Definition at line 213 of file m_ssl.php.

213  {
214  global $db;
215  $db->query("SELECT sd.id FROM sub_domaines sd, domaines_type dt WHERE dt.name=sd.type AND dt.only_dns=0 AND
216 (CONCAT(sd.sub,IF(sd.sub!='','.',''),sd.domaine)=?
217 OR CONCAT('*.',SUBSTRING(CONCAT(sd.sub,IF(sd.sub!='','.',''),sd.domaine),
218 INSTR(CONCAT(sd.sub,IF(sd.sub!='','.',''),sd.domaine),'.')+1))=?
219 );",
220  array($fqdn,$fqdn));
221  $ids=array();
222  while ($db->next_record()) {
223  $ids[]=$db->Record["id"];
224  }
225  return $ids;
226  }

Referenced by cron_new_certs().

◆ update_specials_match()

m_ssl::update_specials_match (   $id,
  $fqdn 
)

update special system certificate that matches the cert fqdn:

Definition at line 154 of file m_ssl.php.

154  {
155  global $L_FQDN;
156 
157  if ($this->fqdnmatch($fqdn,$L_FQDN)) {
158  // new certificate for the panel
159  $this->copycert("alternc-panel",$id);
160  exec("service apache2 reload");
161  }
162  $variables=array("fqdn_dovecot","fqdn_postfix","fqdn_proftpd","fqdn_mailman");
163  foreach($variables as $var) {
164  $value = variable_get($var,null);
165  if ($value) {
166  if ($this->fqdnmatch($fqdn,$value)) {
167  $this->copycert("alternc-".substr($var,5),$id);
168  exec("service ".substr($var,5)." reload");
169  }
170  }
171  }
172 
173  }
fqdnmatch($cert, $fqdn)
Definition: m_ssl.php:141
copycert($target, $id)
copy a certificate (by its ID) to the system files set the correct permissions try to minimize zero-f...
Definition: m_ssl.php:181

References copycert(), and fqdnmatch().

Referenced by cron_new_certs().

◆ updateDomain()

m_ssl::updateDomain (   $action,
  $type,
  $fqdn,
  $mail = 0,
  $value = "" 
)

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)

Definition at line 590 of file m_ssl.php.

590  {
591  global $db, $msg, $dom;
592  $msg->log("ssl", "update_domain($action,$type,$fqdn)");
593 
594  // the domain type must be a "dns_only=false" one:
595  if (!($domtype=$dom->domains_type_get($type)) || $domtype["only_dns"]==true) {
596  return; // nothing to do : this domain type does not involve Vhosts
597  }
598 
599  // The 'vhost' type is overloaded with -http, -https, and -both.
600  // If the type starts with vhost, we should match vhost%
601  // Generally, only 'vhost' is used since the overloads are not enabled
602  // for use in the interface.
603  $type_match = $type;
604  if (substr($type, 0, 5) == 'vhost') {
605  $type_match = 'vhost%';
606  }
607  if ($action == "postinst") {
608  $msg->log("ssl", "update_domain:CREATE($action,$type,$fqdn)");
609  $offset = 0;
610  $found = false;
611  do { // try each subdomain (strtok-style) and search them in sub_domaines table:
612  $db->query(
613  "SELECT * FROM sub_domaines WHERE sub=? AND domaine=? AND web_action NOT IN ('','OK') AND type LIKE ?",
614  array(substr($fqdn, 0, $offset), substr($fqdn, $offset + ($offset != 0)), $type_match)
615  );
616  if ($db->next_record()) {
617  $found = true;
618  break;
619  }
620  $offset = strpos($fqdn, ".", $offset+1);
621  //No more dot, we prevent an infinite loop
622  if (!$offset) {
623  break;
624  }
625  } while (true);
626  if (!$found) {
627  echo "FATAL: didn't found fqdn $fqdn in sub_domaines table !\n";
628  return;
629  }
630  // found and $db point to it:
631  $subdom = $db->Record;
632  $TARGET_FILE = "/var/lib/alternc/apache-vhost/" . substr($subdom["compte"], -1) . "/" . $subdom["compte"] . "/" . $fqdn . ".conf";
633  $cert = $this->searchBestCert($subdom,$fqdn);
634  // $cert[crt/key/chain] are path to the proper files
635 
636  // edit apache conf file to set the certificate:
637  $s = file_get_contents($TARGET_FILE);
638  $s = str_replace("%%CRT%%", $cert["crt"], $s);
639  $s = str_replace("%%KEY%%", $cert["key"], $s);
640  if (isset($cert["chain"]) && $cert["chain"]) {
641  $s = str_replace("%%CHAINLINE%%", "SSLCertificateChainFile " . $cert["chain"], $s);
642  } else {
643  $s = str_replace("%%CHAINLINE%%", "", $s);
644  }
645  file_put_contents($TARGET_FILE, $s);
646  // Edit certif_hosts:
647  $db->query("UPDATE sub_domaines SET certificate_id=? WHERE id=?;",array($cert["id"], $subdom["id"]));
648  } // action==create
649 
650  }
searchBestCert($subdom, $fqdn)
Search for the best certificate for a user and a fqdn Return a hash with crt, key and maybe chain.
Definition: m_ssl.php:690

References searchBestCert().

◆ write_cert_file()

m_ssl::write_cert_file (   $cert)

Write certificate file into KEY_REPOSITORY.

Parameters
$certarray an array with ID sslcrt sslkey sslchain

Definition at line 713 of file m_ssl.php.

713  {
714  // we split the certificates by 1000
715  $CRTDIR = self::KEY_REPOSITORY . "/" . floor($cert["id"]/1000);
716  @mkdir($CRTDIR,0750,true);
717  // set the proper permissions on the Key Repository folder and children :
718  chown(self::KEY_REPOSITORY,"root");
719  chgrp(self::KEY_REPOSITORY,"ssl-cert");
720  chmod(self::KEY_REPOSITORY,0750);
721  chown($CRTDIR,"root");
722  chgrp($CRTDIR,"ssl-cert");
723  chmod($CRTDIR,0750);
724 
725  if (
726  !file_exists($CRTDIR . "/" . $cert["id"].".pem") ||
727  !file_exists($CRTDIR . "/" . $cert["id"].".key")) {
728  // write the files (first time we use a certificate)
729  file_put_contents($CRTDIR . "/" . $cert["id"].".pem", $cert["sslcrt"]);
730  file_put_contents($CRTDIR . "/" . $cert["id"].".key", $cert["sslkey"]);
731  // set the proper rights on those files :
732  chown($CRTDIR . "/" . $cert["id"].".pem","root");
733  chgrp($CRTDIR . "/" . $cert["id"].".pem","ssl-cert");
734  chmod($CRTDIR . "/" . $cert["id"].".pem",0640);
735  chown($CRTDIR . "/" . $cert["id"].".key","root");
736  chgrp($CRTDIR . "/" . $cert["id"].".key","ssl-cert");
737  chmod($CRTDIR . "/" . $cert["id"].".key",0640);
738  if (isset($cert["sslchain"]) && $cert["sslchain"]) {
739  file_put_contents($CRTDIR . "/" . $cert["id"] . ".chain", $cert["sslchain"]);
740  chown($CRTDIR . "/" . $cert["id"].".chain","root");
741  chgrp($CRTDIR . "/" . $cert["id"].".chain","ssl-cert");
742  chmod($CRTDIR . "/" . $cert["id"].".chain",0640);
743  }
744  }
745  }

Referenced by hook_updatedomains_web_before(), and searchBestCert().

Member Data Documentation

◆ $error

m_ssl::$error = ""

Definition at line 37 of file m_ssl.php.

◆ FILTER_EXPIRED

const m_ssl::FILTER_EXPIRED = 4

Definition at line 43 of file m_ssl.php.

Referenced by get_list().

◆ FILTER_OK

const m_ssl::FILTER_OK = 2

Definition at line 42 of file m_ssl.php.

Referenced by get_list().

◆ FILTER_PENDING

const m_ssl::FILTER_PENDING = 1

Definition at line 41 of file m_ssl.php.

◆ KEY_REPOSITORY

const m_ssl::KEY_REPOSITORY = "/var/lib/alternc/ssl/private"

Definition at line 45 of file m_ssl.php.

◆ SPECIAL_CERTIFICATE_ID_PATH

const m_ssl::SPECIAL_CERTIFICATE_ID_PATH = "/var/lib/alternc/ssl/special_id.json"

Definition at line 46 of file m_ssl.php.

◆ STATUS_EXPIRED

const m_ssl::STATUS_EXPIRED = 99

Definition at line 35 of file m_ssl.php.

Referenced by get_list().

◆ STATUS_OK

const m_ssl::STATUS_OK = 1

Definition at line 34 of file m_ssl.php.

Referenced by get_list().

◆ STATUS_PENDING

const m_ssl::STATUS_PENDING = 0

Definition at line 33 of file m_ssl.php.

Referenced by get_list().


The documentation for this class was generated from the following file: