PHP Classes

File: src/LocalCACertBuilder.php

Recommend this page to a friend!
  Classes of Scott Arciszewski   Certainty   src/LocalCACertBuilder.php   Download  
File: src/LocalCACertBuilder.php
Role: Class source
Content type: text/plain
Description: Class source
Class: Certainty
Manage SSL certificate authority file used by PHP
Author: By
Last change:
Date: 6 years ago
Size: 11,114 bytes
 

Contents

Class file image Download
<?php namespace ParagonIE\Certainty; use ParagonIE\Certainty\Exception\CryptoException; use ParagonIE\Certainty\Exception\EncodingException; use ParagonIE\Certainty\Exception\FilesystemException; use ParagonIE\Certainty\Exception\InvalidResponseException; use ParagonIE\ConstantTime\Base64UrlSafe; use ParagonIE\ConstantTime\Hex; /** * Class LocalCACertBuilder * @package ParagonIE\Certainty */ class LocalCACertBuilder extends Bundle { /** * @var string $chronicleClientId */ protected $chronicleClientId = ''; /** * @var string $chroniclePublicKey */ protected $chroniclePublicKey = ''; /** * @var string $chronicleRepoName */ protected $chronicleRepoName = 'paragonie/certainty'; /** * @var string $chronicleUrl */ protected $chronicleUrl = ''; /** * @var string $contents */ protected $contents = ''; /** * @var string $original */ protected $original = ''; /** * @var string $outputPem */ protected $outputPem = ''; /** * @var string $outputJson */ protected $outputJson = ''; /** * @var string $secretKey */ protected $secretKey = ''; /** * @param Bundle $old * @return self */ public static function fromBundle(Bundle $old) { $new = new static( $old->getFilePath(), $old->getSha256Sum(), $old->getSignature() ); $new->customValidator = $old->getValidator(); return $new; } /** * Load the original bundle's contents. * * @return self * @throws FilesystemException */ public function loadOriginal() { /** @var string original */ $this->original = \file_get_contents($this->filePath); if (!\is_string($this->original)) { throw new FilesystemException('Could not read contents of CACert file provided.'); } return $this; } /** * Append a CACert file, containing your in-house certificates, to the bundle * being compiled. * * @param string $path * @return self * @throws FilesystemException */ public function appendCACertFile($path = '') { if (!$this->original) { $this->loadOriginal(); } if (!$this->contents) { $this->contents = $this->original . "\n"; } $contents = \file_get_contents($path); if (!\is_string($contents)) { throw new FilesystemException('Could not read contents of CACert file provided.'); } $this->contents .= $contents . "\n"; return $this; } /** * Publish the most recent CACert information to the local Chronicle. * * @param string $sha256sum * @param string $signature * @return string * * @throws EncodingException * @throws \Exception */ protected function commitToChronicle($sha256sum, $signature) { if (empty($this->chronicleUrl) || empty($this->chroniclePublicKey) || empty($this->chronicleClientId)) { return ''; } /** @var string $body */ $body = \json_encode( [ 'repository' => $this->chronicleRepoName, 'sha256' => $sha256sum, 'signature' => $signature, 'time' => (new \DateTime())->format(\DateTime::ATOM) ], JSON_PRETTY_PRINT ); if (!\is_string($body)) { throw new EncodingException('Could not build a valid JSON message.'); } $signature = \ParagonIE_Sodium_Compat::crypto_sign_detached($body, $this->secretKey); $http = Certainty::getGuzzleClient(); $response = $http->post( $this->chronicleUrl . '/publish', [ 'headers' => [ Certainty::CHRONICLE_CLIENT_ID => $this->chronicleClientId, Certainty::ED25519_HEADER => Base64UrlSafe::encode($signature) ], 'body' => $body, ] ); $responseBody = (string) $response->getBody(); $validSig = false; foreach ($response->getHeader(Certainty::ED25519_HEADER) as $sigLine) { /** @var string $sig */ $sig = Base64UrlSafe::decode($sigLine); $validSig = $validSig || \ParagonIE_Sodium_Compat::crypto_sign_verify_detached( $sig, $responseBody, $this->chroniclePublicKey ); } if (!$validSig) { throw new InvalidResponseException('No valid signature for Chronicle response.'); } /** @var array $json */ $json = \json_decode($responseBody, true); if (!\is_array($json)) { return ''; } if (!isset($json['results'])) { return ''; } if (!isset($json['results']['summaryhash'])) { return ''; } return (string) $json['results']['summaryhash']; } /** * Get the public key. * * @param bool $raw * @return string * @throws \Error * @throws \Exception */ public function getPublicKey($raw = false) { if ($raw) { return \ParagonIE_Sodium_Compat::crypto_sign_publickey_from_secretkey($this->secretKey); } return Hex::encode( \ParagonIE_Sodium_Compat::crypto_sign_publickey_from_secretkey($this->secretKey) ); } /** * Sign and save the combined CA-Cert file. * * @return bool * @throws EncodingException * @throws FilesystemException * @throws \Exception */ public function save() { if (!$this->secretKey) { throw new \Exception('No signing key provided.'); } if (!$this->outputJson) { throw new \Exception('No output file path for JSON data specified.'); } if (!$this->outputPem) { throw new \Exception('No output file path for combined certificates specified.'); } /** @var string $return */ $return = \file_put_contents($this->outputPem, $this->contents); if (!\is_int($return)) { throw new FilesystemException('Could not save PEM file.'); } $sha256sum = \hash('sha256', $this->contents); $signature = \ParagonIE_Sodium_Compat::crypto_sign_detached($this->contents, $this->secretKey); if (\file_exists($this->outputJson)) { /** @var string $fileData */ $fileData = \file_get_contents($this->outputJson); $json = \json_decode($fileData, true); if (!\is_array($json)) { throw new EncodingException('Invalid JSON data stored in file.'); } } else { $json = []; } $pieces = \explode('/', \trim($this->outputPem, '/')); // Put at the front of the array $entry = [ 'custom' => \get_class($this->customValidator), 'date' => \date('Y-m-d'), 'file' => \array_pop($pieces), 'sha256' => $sha256sum, 'signature' => Hex::encode($signature) ]; $chronicleHash = $this->commitToChronicle($sha256sum, $signature); if (!empty($chronicleHash)) { $entry['chronicle'] = $chronicleHash; } \array_unshift($json, $entry); $jsonSave = \json_encode($json, JSON_PRETTY_PRINT); if (!\is_string($jsonSave)) { throw new EncodingException(\json_last_error_msg()); } $this->sha256sum = $sha256sum; $this->signature = $signature; $return = \file_put_contents($this->outputJson, $jsonSave); return \is_int($return); } /** * Configure the local Chronicle. * * @param string $url * @param string $publicKey * @param string $clientId * @param string $repository * @return $this * @throws CryptoException */ public function setChronicle($url = '', $publicKey = '', $clientId = '', $repository = 'paragonie/certainty') { if (\ParagonIE_Sodium_Core_Util::strlen($publicKey) === 64) { /** @var string $publicKey */ $publicKey = Hex::decode($publicKey); if (!\is_string($publicKey)) { throw new CryptoException('Signing secret keys must be SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES bytes long.'); } } elseif (\ParagonIE_Sodium_Core_Util::strlen($publicKey) !== 32) { throw new CryptoException('Signing secret keys must be SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES bytes long.'); } $this->chronicleClientId = $clientId; $this->chroniclePublicKey = $publicKey; $this->chronicleUrl = $url; $this->chronicleRepoName = $repository; return $this; } /** * Specify the fully qualified class name for your custom * Validator class. * * @param string $string * @return self * @throws \TypeError */ public function setCustomValidator($string = '') { if (\class_exists($string)) { $newClass = new $string(); if (!($newClass instanceof Validator)) { throw new \TypeError('Invalid validator class'); } $this->customValidator = $newClass; } return $this; } /** * Specify the full path of the file that the combined CA-cert will be * written to when save() is invoked. * * @param string $string * @return self */ public function setOutputPemFile($string = '') { $this->outputPem = $string; return $this; } /** * Specify the full path of the file that will contain the updated * sha256/Ed25519 metadata. * * @param string $string * @return self */ public function setOutputJsonFile($string = '') { $this->outputJson = $string; return $this; } /** * Specify the signing key to be used. * * @param string $secretKey * @return self * @throws CryptoException */ public function setSigningKey($secretKey = '') { // Handle hex-encoded strings. if (\ParagonIE_Sodium_Core_Util::strlen($secretKey) === 128) { /** @var string $secretKey */ $secretKey = Hex::decode($secretKey); if (!\is_string($secretKey)) { throw new CryptoException('Signing secret keys must be SODIUM_CRYPTO_SIGN_SECRETKEYBYTES bytes long.'); } } elseif (\ParagonIE_Sodium_Core_Util::strlen($secretKey) !== 64) { throw new CryptoException('Signing secret keys must be SODIUM_CRYPTO_SIGN_SECRETKEYBYTES bytes long.'); } $this->secretKey = $secretKey; return $this; } /** * Don't leak secret keys. * * @return array */ public function __debugInfo() { return []; } }