protected function execute(InputInterface $input, OutputInterface $output) { $error = $output->getErrorOutput(); $path = $input->getArgument("path"); $includeRoot = $input->getOption("include-root"); if (!\file_exists($path)) { throw new \Exception("File {$path} doesn't exist"); } try { list($inform, $format) = \detectCertFormat($path); $cert = \parseFormattedCert(\readCertificate($path, $inform, $format)); } catch (\Exception $e) { $err = implode("\n", $e->output); throw new \Exception("Unable to load certificate: {$err}"); } // perform some basic checks if (\isExpired($cert)) { $error->writeln("Certificate has expired"); } if (\areCertsLinked($cert, $cert)) { throw new \Exception("Self-signed or CA cert"); } // this can fail with an exception $out = \buildChain($cert, $path, $includeRoot); $output->write($out); }
function buildChain($cert, $certPath, $includeRoot = false) { if (isExpired($cert)) { throw new Exception("Certificate has expired"); } if (areCertsLinked($cert, $cert)) { throw new Exception("Self-signed or CA cert"); } $uris = $cert["issuers"]; if (!$uris) { throw new Exception("Certificate doesn't specify issuers"); } $c = $cert; $chain = []; while (sizeof($uris) > 0) { $old = $c; $uri = array_shift($uris); $path = downloadIssuer($uri); list($inform, $format) = detectCertFormat($path); $c = parseFormattedCert(readCertificate($path, $inform, $format)); if (isExpired($c)) { throw new Exception("Expired intermediate in the chain"); } if (areCertsLinked($c, $c)) { break; } if (!areCertsLinked($c, $old)) { $msg = "Intermediate doesn't match previous certificate in the chain"; throw new Exception($msg); } $chain[] = $path; if (isset($c["issuers"])) { foreach ($c["issuers"] as $i) { $uris[] = $i; // we don't currently have a good way of handling multiple // issuers break; } } } // we are at the end of the chain, see if there's matching root CA $cacheDir = __DIR__ . '/cache'; $adapter = new File($cacheDir); $adapter->setOption('ttl', 600); $cache = new Cache($adapter); if (!$cache->get(md5($path) . "-root")) { $root = findMatchingRoot($c); $chain[] = $root; $cache->set(md5($path) . "-root", $root); } else { $chain[] = $cache->get(md5($path) . "-root"); } // build certificate bundle foreach ($chain as $i => $path) { list($inform, $format) = detectCertFormat($path); $cmd = sprintf("openssl x509 -inform %s -outform pem -in %s -out %s", escapeshellarg($inform), escapeshellarg($path), escapeshellarg(__DIR__ . "/tmp/" . sha1($cert["subject"]) . "-{$i}.pem")); exec($cmd); } unlink(__DIR__ . "/tmp/bundle.crt"); foreach ($chain as $i => $path) { file_put_contents(__DIR__ . "/tmp/bundle.crt", file_get_contents(__DIR__ . "/tmp/" . sha1($cert["subject"]) . "-{$i}.pem"), FILE_APPEND); } // verify the chain is valid $cmd = sprintf("openssl verify -verbose -purpose sslserver -CAfile %s/tmp/bundle.crt %s", __DIR__, escapeshellarg($certPath)); try { execute($cmd); } catch (Exception $e) { $err = implode("\n", $e->output); throw new Exception("Can't verify the bundle: {$err}"); } // extract the original cert (it might contain some, or all, parts of the // chain already) $cmd = sprintf("openssl x509 -inform pem -outform pem -in %s", $certPath); $out = implode("\n", execute($cmd)); $out .= "\n"; if (!$includeRoot) { array_pop($chain); } foreach ($chain as $i => $path) { $out .= file_get_contents(__DIR__ . "/tmp/" . sha1($cert["subject"]) . "-{$i}.pem"); unlink(__DIR__ . "/tmp/" . sha1($cert["subject"]) . "-{$i}.pem"); } return $out; }