hostname = $config->hostname; $this->tls_certfile = $config->tls_certfile; $this->tls_keyfile = $config->tls_keyfile; $this->key_passphrase = $config->key_passphrase; if ($logger !== null) { $this->logger = $logger; } else { $this->logger = new Logger('orbit-cert'); } if ($config->getIsDevelopmentServer()) { $this->initDevelopment(); } else { $this->initProduction(); } } /** * Initialize for development mode * * If the cert files do not exist, generate a new self-signed cert * * @return bool */ public function initDevelopment(): bool { $this->logger->debug("Initialize cert for development mode."); if ($this->tls_certfile == '') { $this->tls_certfile = sprintf("certs/%s.cert.pem", $this->hostname); } if ($this->tls_keyfile == '') { $this->tls_keyfile = sprintf("certs/%s.key.pem", $this->hostname); } if (file_exists($this->tls_certfile) && file_exists($this->tls_keyfile)) { $this->logger->info(sprintf("Using existing cert file '%s'", $this->tls_certfile)); $this->logger->info(sprintf("Using existing key file '%s'", $this->tls_keyfile)); } else { $this->logger->info(sprintf("Generating new cert file '%s'", $this->tls_certfile)); $this->logger->info(sprintf("Generating new key file '%s'", $this->tls_keyfile)); if (file_exists($this->tls_certfile)) { $this->logger->warning(sprintf("Warning! May overwrite existing cert file '%s'", $this->tls_certfile)); } if (file_exists($this->tls_keyfile)) { $this->logger->warning(sprintf("Warning! May overwrite existing key file '%s'", $this->tls_keyfile)); } $this->generateCert(); } return true; } /** * Initialize for production mode * * Cert/key files must be provided. If fails, generates exception * * @return bool */ public function initProduction(): bool { $this->logger->debug("Initialize cert for production mode."); $errors = []; if ($this->tls_certfile == '') { $errors[] = "Missing required cert file: use --tls-cert to specify;"; } if ($this->tls_keyfile == '') { $errors[] = "Missing required key file: use --tls-key to specify;"; } if ($this->tls_certfile && !file_exists($this->tls_certfile)) { $errors[] = sprintf("Cert file '%s' does not exist or is not readable!", $this->tls_certfile); } if ($this->tls_keyfile && !file_exists($this->tls_keyfile)) { $errors[] = sprintf("Key file '%s' does not exist or is not readable!", $this->tls_keyfile); } if (count($errors)) { $errors[] = "\nTry running with --dev to generate a self-signed cert automatically."; $this->logger->alert(implode("\n", $errors)); throw new \Exception("\n" . implode("\n", $errors)); } $this->logger->debug(sprintf("Using cert file '%s'", $this->tls_certfile)); $this->logger->debug(sprintf("Using key file '%s'", $this->tls_keyfile)); return true; } /** * Generate a self-signed cert * * @return void */ private function generateCert(): void { // Certificate data $dn = [ "countryName" => "UK", "stateOrProvinceName" => "X", "localityName" => "X", "organizationName" => "X", "organizationalUnitName" => "X", "commonName" => $this->hostname, "emailAddress" => "X", ]; $days_valid = 365; $san_domains = ["DNS:" . $this->hostname, "IP:127.0.0.1", "IP:0.0.0.0", "IP:::1"]; $ssl_config = $this->createOpenSslConf($san_domains); $csr_config = ['digest_alg' => 'sha256', 'req_extensions' => 'v3_req', 'config' => $ssl_config]; $cert_config = ['digest_alg' => 'sha256', 'x509_extensions' => 'usr_cert', 'config' => $ssl_config]; // Generate certificate $private_key = openssl_pkey_new([ 'private_key_type' => OPENSSL_KEYTYPE_RSA, 'private_key_bits' => 2048 ]); $cert = openssl_csr_new($dn, $private_key, $csr_config); $cert = openssl_csr_sign($cert, null, $private_key, $days_valid, $cert_config); // Generate PEM files $pem = []; openssl_x509_export($cert, $pem[0]); openssl_pkey_export($private_key, $pem[1], $this->key_passphrase); // Ensure dir exists for cert files $this->ensureDirExists($this->tls_certfile); $this->ensureDirExists($this->tls_keyfile); // Save PEM files file_put_contents($this->tls_certfile, $pem[0]); file_put_contents($this->tls_keyfile, $pem[1]); // Remove temp sslconf file unlink($ssl_config); } /** * createOpenSslConf * * Creating this temp file to be used in the CSR and the cert signing is * required to add csr request and x509 extensions into the cert in order * to include the subject alternative names * * @param array $san_domains * @return string Filename */ private function createOpenSslConf(array $san_domains = []): string { $san_domains_string = implode(",", $san_domains); $str = <<