Initial Commit

This commit is contained in:
David Stone
2024-11-30 18:24:12 -07:00
commit e8f7955c1c
5432 changed files with 1397750 additions and 0 deletions

View File

@ -0,0 +1,388 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Socket;
final class ClientTlsContext
{
public const TLSv1_0 = \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT;
public const TLSv1_1 = \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
public const TLSv1_2 = \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
public const TLSv1_3 = \PHP_VERSION_ID >= 70400 ? \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT : 0;
private const TLS_VERSIONS = \PHP_VERSION_ID >= 70400 ? ['TLSv1.0' => self::TLSv1_0, 'TLSv1.1' => self::TLSv1_1, 'TLSv1.2' => self::TLSv1_2, 'TLSv1.3' => self::TLSv1_3] : ['TLSv1.0' => self::TLSv1_0, 'TLSv1.1' => self::TLSv1_1, 'TLSv1.2' => self::TLSv1_2];
/** @var int */
private $minVersion = self::TLSv1_0;
/** @var string|null */
private $peerName;
/** @var bool */
private $verifyPeer = \true;
/** @var int */
private $verifyDepth = 10;
/** @var string|null */
private $ciphers;
/** @var string|null */
private $caFile;
/** @var string|null */
private $caPath;
/** @var bool */
private $capturePeer = \false;
/** @var bool */
private $sniEnabled = \true;
/** @var int */
private $securityLevel = 2;
/** @var Certificate|null */
private $certificate;
/** @var string[] */
private $alpnProtocols = [];
public function __construct(string $peerName)
{
$this->peerName = $peerName;
}
/**
* Minimum TLS version to negotiate.
*
* Defaults to TLS 1.0.
*
* @param int $version One of the `ClientTlsContext::TLSv*` constants.
*
* @return self Cloned, modified instance.
* @throws \Error If an invalid minimum version is given.
*/
public function withMinimumVersion(int $version) : self
{
if (!\in_array($version, self::TLS_VERSIONS, \true)) {
throw new \Error(\sprintf('Invalid minimum version, only %s allowed', \implode(', ', \array_keys(self::TLS_VERSIONS))));
}
$clone = clone $this;
$clone->minVersion = $version;
return $clone;
}
/**
* Returns the minimum TLS version to negotiate.
*
* @return int
*/
public function getMinimumVersion() : int
{
return $this->minVersion;
}
/**
* Expected name of the peer.
*
* @param string $peerName
*
* @return self Cloned, modified instance.
*/
public function withPeerName(string $peerName) : self
{
$clone = clone $this;
$clone->peerName = $peerName;
return $clone;
}
/**
* @return null|string Expected name of the peer or `null` if such an expectation doesn't exist.
*/
public function getPeerName() : ?string
{
return $this->peerName;
}
/**
* Enable peer verification.
*
* @return self Cloned, modified instance.
*/
public function withPeerVerification() : self
{
$clone = clone $this;
$clone->verifyPeer = \true;
return $clone;
}
/**
* Disable peer verification, this is the default for servers.
*
* Warning: You usually shouldn't disable this setting for clients, because it allows active MitM attackers to
* intercept the communication and change it without anyone noticing.
*
* @return self Cloned, modified instance.
*/
public function withoutPeerVerification() : self
{
$clone = clone $this;
$clone->verifyPeer = \false;
return $clone;
}
/**
* @return bool Whether peer verification is enabled.
*/
public function hasPeerVerification() : bool
{
return $this->verifyPeer;
}
/**
* Maximum chain length the peer might present including the certificates in the local trust store.
*
* @param int $verifyDepth Maximum length of the certificate chain.
*
* @return self Cloned, modified instance.
*/
public function withVerificationDepth(int $verifyDepth) : self
{
if ($verifyDepth < 0) {
throw new \Error("Invalid verification depth ({$verifyDepth}), must be greater than or equal to 0");
}
$clone = clone $this;
$clone->verifyDepth = $verifyDepth;
return $clone;
}
/**
* @return int Maximum length of the certificate chain.
*/
public function getVerificationDepth() : int
{
return $this->verifyDepth;
}
/**
* List of ciphers to negotiate, the server's order is always preferred.
*
* @param string|null $ciphers List of ciphers in OpenSSL's format (colon separated).
*
* @return self Cloned, modified instance.
*/
public function withCiphers(string $ciphers = null) : self
{
$clone = clone $this;
$clone->ciphers = $ciphers;
return $clone;
}
/**
* @return string List of ciphers in OpenSSL's format (colon separated).
*/
public function getCiphers() : string
{
return $this->ciphers ?? \OPENSSL_DEFAULT_STREAM_CIPHERS;
}
/**
* CAFile to check for trusted certificates.
*
* @param string|null $cafile Path to the file or `null` to unset.
*
* @return self Cloned, modified instance.
*/
public function withCaFile(string $cafile = null) : self
{
$clone = clone $this;
$clone->caFile = $cafile;
return $clone;
}
/**
* @return null|string Path to the file if one is set, otherwise `null`.
*/
public function getCaFile() : ?string
{
return $this->caFile;
}
/**
* CAPath to check for trusted certificates.
*
* @param string|null $capath Path to the file or `null` to unset.
*
* @return self Cloned, modified instance.
*/
public function withCaPath(string $capath = null) : self
{
$clone = clone $this;
$clone->caPath = $capath;
return $clone;
}
/**
* @return null|string Path to the file if one is set, otherwise `null`.
*/
public function getCaPath() : ?string
{
return $this->caPath;
}
/**
* Capture the certificates sent by the peer.
*
* Note: This is the chain as sent by the peer, NOT the verified chain.
*
* @return self Cloned, modified instance.
*/
public function withPeerCapturing() : self
{
$clone = clone $this;
$clone->capturePeer = \true;
return $clone;
}
/**
* Don't capture the certificates sent by the peer.
*
* @return self Cloned, modified instance.
*/
public function withoutPeerCapturing() : self
{
$clone = clone $this;
$clone->capturePeer = \false;
return $clone;
}
/**
* @return bool Whether to capture the certificates sent by the peer.
*/
public function hasPeerCapturing() : bool
{
return $this->capturePeer;
}
/**
* Enable SNI.
*
* @return self Cloned, modified instance.
*/
public function withSni() : self
{
$clone = clone $this;
$clone->sniEnabled = \true;
return $clone;
}
/**
* Disable SNI.
*
* @return self Cloned, modified instance.
*/
public function withoutSni() : self
{
$clone = clone $this;
$clone->sniEnabled = \false;
return $clone;
}
/**
* @return bool Whether SNI is enabled or not.
*/
public function hasSni() : bool
{
return $this->sniEnabled;
}
/**
* Security level to use.
*
* Requires OpenSSL 1.1.0 or higher.
*
* @param int $level Must be between 0 and 5.
*
* @return self Cloned, modified instance.
*/
public function withSecurityLevel(int $level) : self
{
// See https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_security_level.html
// Level 2 is not recommended, because of SHA-1 by that document,
// but SHA-1 should be phased out now on general internet use.
// We therefore default to level 2.
if ($level < 0 || $level > 5) {
throw new \Error("Invalid security level ({$level}), must be between 0 and 5.");
}
if (!hasTlsSecurityLevelSupport()) {
throw new \Error("Can't set a security level, as PHP is compiled with OpenSSL < 1.1.0.");
}
$clone = clone $this;
$clone->securityLevel = $level;
return $clone;
}
/**
* @return int Security level between 0 and 5. Always 0 for OpenSSL < 1.1.0.
*/
public function getSecurityLevel() : int
{
// 0 is equivalent to previous versions of OpenSSL and just does nothing
if (!hasTlsSecurityLevelSupport()) {
return 0;
}
return $this->securityLevel;
}
/**
* Client certificate to use, if key is no present it assumes it is present in the same file as the certificate.
*
* @param Certificate $certificate Certificate and private key info
*
* @return self Cloned, modified instance.
*/
public function withCertificate(Certificate $certificate = null) : self
{
$clone = clone $this;
$clone->certificate = $certificate;
return $clone;
}
public function getCertificate() : ?Certificate
{
return $this->certificate;
}
/**
* @param string[] $protocols
*
* @return self Cloned, modified instance.
*/
public function withApplicationLayerProtocols(array $protocols) : self
{
if (!hasTlsAlpnSupport()) {
throw new \Error("Can't set an application layer protocol list, as PHP is compiled with OpenSSL < 1.0.2.");
}
foreach ($protocols as $protocol) {
if (!\is_string($protocol)) {
throw new \TypeError("Protocol names must be strings");
}
}
$clone = clone $this;
$clone->alpnProtocols = $protocols;
return $clone;
}
/**
* @return string[]
*/
public function getApplicationLayerProtocols() : array
{
return $this->alpnProtocols;
}
/**
* Converts this TLS context into PHP's equivalent stream context array.
*
* @return array Stream context array compatible with PHP's streams.
*/
public function toStreamContextArray() : array
{
$options = ['crypto_method' => $this->toStreamCryptoMethod(), 'peer_name' => $this->peerName, 'verify_peer' => $this->verifyPeer, 'verify_peer_name' => $this->verifyPeer, 'verify_depth' => $this->verifyDepth, 'ciphers' => $this->ciphers ?? \OPENSSL_DEFAULT_STREAM_CIPHERS, 'capture_peer_cert' => $this->capturePeer, 'capture_peer_cert_chain' => $this->capturePeer, 'SNI_enabled' => $this->sniEnabled];
if ($this->certificate !== null) {
$options['local_cert'] = $this->certificate->getCertFile();
if ($this->certificate->getCertFile() !== $this->certificate->getKeyFile()) {
$options['local_pk'] = $this->certificate->getKeyFile();
}
}
if ($this->caFile !== null) {
$options['cafile'] = $this->caFile;
}
if ($this->caPath !== null) {
$options['capath'] = $this->caPath;
}
if (hasTlsSecurityLevelSupport()) {
$options['security_level'] = $this->securityLevel;
}
if (!empty($this->alpnProtocols)) {
$options['alpn_protocols'] = \implode(',', $this->alpnProtocols);
}
return ['ssl' => $options];
}
/**
* @return int Crypto method compatible with PHP's streams.
*/
public function toStreamCryptoMethod() : int
{
switch ($this->minVersion) {
case self::TLSv1_0:
return self::TLSv1_0 | self::TLSv1_1 | self::TLSv1_2 | self::TLSv1_3;
case self::TLSv1_1:
return self::TLSv1_1 | self::TLSv1_2 | self::TLSv1_3;
case self::TLSv1_2:
return self::TLSv1_2 | self::TLSv1_3;
case self::TLSv1_3:
return self::TLSv1_3;
default:
throw new \RuntimeException('Unknown minimum TLS version: ' . $this->minVersion);
}
}
}