Skip to content

Commit

Permalink
Add security_level support, default to level 2
Browse files Browse the repository at this point in the history
Fixes #21.
  • Loading branch information
kelunik committed Nov 3, 2017
1 parent aceba3b commit 7c2ba04
Show file tree
Hide file tree
Showing 4 changed files with 298 additions and 1 deletion.
162 changes: 161 additions & 1 deletion lib/ClientTlsContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,18 @@ final class ClientTlsContext {
private $caPath = null;
private $capturePeer = false;
private $sniEnabled = true;

private $securityLevel = 2;

/**
* Minimum TLS version to negotiate.
*
* Defaults to TLS 1.0.
*
* @param int $version `ServerTlsContext::TLSv1_0`, `ServerTlsContext::TLSv1_1`, or `ServerTlsContext::TLSv1_2`.
*
* @return ServerTlsContext Cloned, modified instance.
* @throws \Error If an invalid minimum version is given.
*/
public function withMinimumVersion(int $version): self {
if ($version !== self::TLSv1_0 && $version !== self::TLSv1_1 && $version !== self::TLSv1_2) {
throw new \Error("Invalid minimum version, only TLSv1.0, TLSv1.1 or TLSv1.2 allowed");
Expand All @@ -28,39 +39,74 @@ public function withMinimumVersion(int $version): self {
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|null $peerName
*
* @return ServerTlsContext Cloned, modified instance.
*/
public function withPeerName(string $peerName = null): 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() {
return $this->peerName;
}

/**
* Enable peer verification.
*
* @return ServerTlsContext Cloned, modified instance.
*/
public function withPeerVerification(): self {
$clone = clone $this;
$clone->verifyPeer = true;

return $clone;
}

/**
* Disable peer verification, this is the default for servers.
*
* @return ServerTlsContext 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 ServerTlsContext 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");
Expand All @@ -72,79 +118,186 @@ public function withVerificationDepth(int $verifyDepth): self {
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 ServerTlsContext 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 ServerTlsContext 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() {
return $this->caFile;
}

/**
* CAPath to check for trusted certificates.
*
* @param string|null $capath Path to the file or `null` to unset.
*
* @return ServerTlsContext 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() {
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 ServerTlsContext 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 ServerTlsContext 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 ServerTlsContext Cloned, modified instance.
*/
public function withSni(): self {
$clone = clone $this;
$clone->sniEnabled = true;

return $clone;
}

/**
* Disable SNI.
*
* @return ServerTlsContext 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 ServerTlsContext 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 (\OPENSSL_VERSION_NUMBER < 0x10100000) {
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 (\OPENSSL_VERSION_NUMBER < 0x10100000) {
return 0;
}

return $this->securityLevel;
}

/**
* 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(),
Expand All @@ -166,9 +319,16 @@ public function toStreamContextArray(): array {
$options["capath"] = $this->caPath;
}

if (\OPENSSL_VERSION_NUMBER >= 0x10100000) {
$options["security_level"] = $this->securityLevel;
}

return ["ssl" => $options];
}

/**
* @return int Crypto method compatible with PHP's streams.
*/
public function toStreamCryptoMethod(): int {
// -2 to clear client flag and then make all lower versions bits 1
return (~($this->minVersion - 2) & \STREAM_CRYPTO_METHOD_ANY_CLIENT) | 1;
Expand Down
45 changes: 45 additions & 0 deletions lib/ServerTlsContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,47 @@ public function getCertificates(): array {
return $this->certificates;
}

/**
* Security level to use.
*
* Requires OpenSSL 1.1.0 or higher.
*
* @param int $level Must be between 0 and 5.
*
* @return ServerTlsContext 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 (\OPENSSL_VERSION_NUMBER < 0x10100000) {
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 (\OPENSSL_VERSION_NUMBER < 0x10100000) {
return 0;
}

return $this->securityLevel;
}

/**
* Converts this TLS context into PHP's equivalent stream context array.
*
Expand Down Expand Up @@ -348,6 +389,10 @@ public function toStreamContextArray(): array {
$options["capath"] = $this->caPath;
}

if (\OPENSSL_VERSION_NUMBER >= 0x10100000) {
$options["security_level"] = $this->securityLevel;
}

return ["ssl" => $options];
}

Expand Down

0 comments on commit 7c2ba04

Please sign in to comment.