This commit is contained in:
2024-08-05 22:57:28 +08:00
commit 0efda6c02a
1779 changed files with 171774 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
composer.lock
vendor/
+80
View File
@@ -0,0 +1,80 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl;
use AcmePhp\Ssl\Exception\CertificateFormatException;
use Webmozart\Assert\Assert;
/**
* Represent a Certificate.
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class Certificate
{
/** @var string */
private $certificatePEM;
/** @var Certificate */
private $issuerCertificate;
public function __construct(string $certificatePEM, self $issuerCertificate = null)
{
Assert::stringNotEmpty($certificatePEM, __CLASS__.'::$certificatePEM should not be an empty string. Got %s');
$this->certificatePEM = $certificatePEM;
$this->issuerCertificate = $issuerCertificate;
}
/**
* @return Certificate[]
*/
public function getIssuerChain(): array
{
$chain = [];
$issuerCertificate = $this->getIssuerCertificate();
while (null !== $issuerCertificate) {
$chain[] = $issuerCertificate;
$issuerCertificate = $issuerCertificate->getIssuerCertificate();
}
return $chain;
}
public function getPEM(): string
{
return $this->certificatePEM;
}
public function getIssuerCertificate(): ?self
{
return $this->issuerCertificate;
}
/**
* @return resource
*/
public function getPublicKeyResource()
{
if (!$resource = openssl_pkey_get_public($this->certificatePEM)) {
throw new CertificateFormatException(sprintf('Failed to convert certificate into public key resource: %s', openssl_error_string()));
}
return $resource;
}
public function getPublicKey(): PublicKey
{
return new PublicKey(openssl_pkey_get_details($this->getPublicKeyResource())['key']);
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl;
/**
* Contains data required to request a certificate.
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class CertificateRequest
{
/** @var DistinguishedName */
private $distinguishedName;
/** @var KeyPair */
private $keyPair;
public function __construct(DistinguishedName $distinguishedName, KeyPair $keyPair)
{
$this->distinguishedName = $distinguishedName;
$this->keyPair = $keyPair;
}
public function getDistinguishedName(): DistinguishedName
{
return $this->distinguishedName;
}
public function getKeyPair(): KeyPair
{
return $this->keyPair;
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl;
/**
* Represent the response to a certificate request.
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class CertificateResponse
{
/** @var CertificateRequest */
private $certificateRequest;
/** @var Certificate */
private $certificate;
public function __construct(CertificateRequest $certificateRequest, Certificate $certificate)
{
$this->certificateRequest = $certificateRequest;
$this->certificate = $certificate;
}
public function getCertificateRequest(): CertificateRequest
{
return $this->certificateRequest;
}
public function getCertificate(): Certificate
{
return $this->certificate;
}
}
+112
View File
@@ -0,0 +1,112 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl;
use Webmozart\Assert\Assert;
/**
* Represent a Distinguished Name.
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class DistinguishedName
{
/** @var string */
private $commonName;
/** @var string */
private $countryName;
/** @var string */
private $stateOrProvinceName;
/** @var string */
private $localityName;
/** @var string */
private $organizationName;
/** @var string */
private $organizationalUnitName;
/** @var string */
private $emailAddress;
/** @var array */
private $subjectAlternativeNames;
public function __construct(
string $commonName,
string $countryName = null,
string $stateOrProvinceName = null,
string $localityName = null,
string $organizationName = null,
string $organizationalUnitName = null,
string $emailAddress = null,
array $subjectAlternativeNames = []
) {
Assert::stringNotEmpty($commonName, __CLASS__.'::$commonName expected a non empty string. Got: %s');
Assert::allStringNotEmpty(
$subjectAlternativeNames,
__CLASS__.'::$subjectAlternativeNames expected an array of non empty string. Got: %s'
);
$this->commonName = $commonName;
$this->countryName = $countryName;
$this->stateOrProvinceName = $stateOrProvinceName;
$this->localityName = $localityName;
$this->organizationName = $organizationName;
$this->organizationalUnitName = $organizationalUnitName;
$this->emailAddress = $emailAddress;
$this->subjectAlternativeNames = array_diff(array_unique($subjectAlternativeNames), [$commonName]);
}
public function getCommonName(): string
{
return $this->commonName;
}
public function getCountryName(): ?string
{
return $this->countryName;
}
public function getStateOrProvinceName(): ?string
{
return $this->stateOrProvinceName;
}
public function getLocalityName(): ?string
{
return $this->localityName;
}
public function getOrganizationName(): ?string
{
return $this->organizationName;
}
public function getOrganizationalUnitName(): ?string
{
return $this->organizationalUnitName;
}
public function getEmailAddress(): ?string
{
return $this->emailAddress;
}
public function getSubjectAlternativeNames(): array
{
return $this->subjectAlternativeNames;
}
}
+19
View File
@@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Exception;
/**
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class AcmeSslException extends \RuntimeException
{
}
@@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Exception;
/**
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class CSRSigningException extends SigningException
{
}
@@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Exception;
/**
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class CertificateFormatException extends ParsingException
{
}
@@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Exception;
/**
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class CertificateParsingException extends ParsingException
{
}
@@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Exception;
/**
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
class DataSigningException extends SigningException
{
}
@@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Exception;
/**
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class KeyFormatException extends ParsingException
{
}
@@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Exception;
/**
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class KeyGenerationException extends AcmeSslException
{
}
@@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Exception;
/**
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class KeyPairGenerationException extends KeyGenerationException
{
}
@@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Exception;
/**
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
class KeyParsingException extends ParsingException
{
}
+19
View File
@@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Exception;
/**
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
class ParsingException extends AcmeSslException
{
}
+19
View File
@@ -0,0 +1,19 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Exception;
/**
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
class SigningException extends AcmeSslException
{
}
@@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Generator;
use AcmePhp\Ssl\PrivateKey;
/**
* Generate random RSA private key using OpenSSL.
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class ChainPrivateKeyGenerator implements PrivateKeyGeneratorInterface
{
/** @var PrivateKeyGeneratorInterface[] */
private $generators;
/**
* @param PrivateKeyGeneratorInterface[] $generators
*/
public function __construct(iterable $generators)
{
$this->generators = $generators;
}
public function generatePrivateKey(KeyOption $keyOption): PrivateKey
{
foreach ($this->generators as $generator) {
if ($generator->supportsKeyOption($keyOption)) {
return $generator->generatePrivateKey($keyOption);
}
}
throw new \LogicException(sprintf('Unable to find a generator for a key option of type %s', \get_class($keyOption)));
}
public function supportsKeyOption(KeyOption $keyOption): bool
{
foreach ($this->generators as $generator) {
if ($generator->supportsKeyOption($keyOption)) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,46 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Generator\DhKey;
use AcmePhp\Ssl\Generator\KeyOption;
use AcmePhp\Ssl\Generator\OpensslPrivateKeyGeneratorTrait;
use AcmePhp\Ssl\Generator\PrivateKeyGeneratorInterface;
use AcmePhp\Ssl\PrivateKey;
use Webmozart\Assert\Assert;
/**
* Generate random DH private key using OpenSSL.
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class DhKeyGenerator implements PrivateKeyGeneratorInterface
{
use OpensslPrivateKeyGeneratorTrait;
public function generatePrivateKey(KeyOption $keyOption): PrivateKey
{
Assert::isInstanceOf($keyOption, DhKeyOption::class);
return $this->generatePrivateKeyFromOpensslOptions([
'private_key_type' => OPENSSL_KEYTYPE_DH,
'dh' => [
'p' => $keyOption->getPrime(),
'g' => $keyOption->getGenerator(),
],
]);
}
public function supportsKeyOption(KeyOption $keyOption): bool
{
return $keyOption instanceof DhKeyOption;
}
}
@@ -0,0 +1,45 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Generator\DhKey;
use AcmePhp\Ssl\Generator\KeyOption;
class DhKeyOption implements KeyOption
{
/** @var string */
private $generator;
/** @var string */
private $prime;
/**
* @param string $prime Hexadecimal representation of the prime
* @param string $generator Hexadecimal representation of the generator: ie. 02
*
* @see https://tools.ietf.org/html/rfc3526 how to choose a prime and generator numbers
*/
public function __construct(string $prime, string $generator = '02')
{
$this->generator = pack('H*', $generator);
$this->prime = pack('H*', $prime);
}
public function getGenerator(): string
{
return $this->generator;
}
public function getPrime(): string
{
return $this->prime;
}
}
@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Generator\DsaKey;
use AcmePhp\Ssl\Generator\KeyOption;
use AcmePhp\Ssl\Generator\OpensslPrivateKeyGeneratorTrait;
use AcmePhp\Ssl\Generator\PrivateKeyGeneratorInterface;
use AcmePhp\Ssl\PrivateKey;
use Webmozart\Assert\Assert;
/**
* Generate random DSA private key using OpenSSL.
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class DsaKeyGenerator implements PrivateKeyGeneratorInterface
{
use OpensslPrivateKeyGeneratorTrait;
public function generatePrivateKey(KeyOption $keyOption): PrivateKey
{
Assert::isInstanceOf($keyOption, DsaKeyOption::class);
return $this->generatePrivateKeyFromOpensslOptions([
'private_key_type' => OPENSSL_KEYTYPE_DSA,
'private_key_bits' => $keyOption->getBits(),
]);
}
public function supportsKeyOption(KeyOption $keyOption): bool
{
return $keyOption instanceof DsaKeyOption;
}
}
@@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Generator\DsaKey;
use AcmePhp\Ssl\Generator\KeyOption;
class DsaKeyOption implements KeyOption
{
/** @var int */
private $bits;
public function __construct(int $bits = 2048)
{
$this->bits = $bits;
}
public function getBits(): int
{
return $this->bits;
}
}
@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Generator\EcKey;
use AcmePhp\Ssl\Generator\KeyOption;
use AcmePhp\Ssl\Generator\OpensslPrivateKeyGeneratorTrait;
use AcmePhp\Ssl\Generator\PrivateKeyGeneratorInterface;
use AcmePhp\Ssl\PrivateKey;
use Webmozart\Assert\Assert;
/**
* Generate random EC private key using OpenSSL.
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class EcKeyGenerator implements PrivateKeyGeneratorInterface
{
use OpensslPrivateKeyGeneratorTrait;
public function generatePrivateKey(KeyOption $keyOption): PrivateKey
{
Assert::isInstanceOf($keyOption, EcKeyOption::class);
return $this->generatePrivateKeyFromOpensslOptions([
'private_key_type' => OPENSSL_KEYTYPE_EC,
'curve_name' => $keyOption->getCurveName(),
]);
}
public function supportsKeyOption(KeyOption $keyOption): bool
{
return $keyOption instanceof EcKeyOption;
}
}
@@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Generator\EcKey;
use AcmePhp\Ssl\Generator\KeyOption;
use Webmozart\Assert\Assert;
class EcKeyOption implements KeyOption
{
/** @var string */
private $curveName;
public function __construct(string $curveName = 'secp384r1')
{
Assert::oneOf($curveName, openssl_get_curve_names(), 'The given curve %s is not supported. Available curves are: %s');
$this->curveName = $curveName;
}
public function getCurveName(): string
{
return $this->curveName;
}
}
+16
View File
@@ -0,0 +1,16 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Generator;
interface KeyOption
{
}
+66
View File
@@ -0,0 +1,66 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Generator;
use AcmePhp\Ssl\Exception\KeyGenerationException;
use AcmePhp\Ssl\Exception\KeyPairGenerationException;
use AcmePhp\Ssl\Generator\DhKey\DhKeyGenerator;
use AcmePhp\Ssl\Generator\DsaKey\DsaKeyGenerator;
use AcmePhp\Ssl\Generator\EcKey\EcKeyGenerator;
use AcmePhp\Ssl\Generator\RsaKey\RsaKeyGenerator;
use AcmePhp\Ssl\Generator\RsaKey\RsaKeyOption;
use AcmePhp\Ssl\KeyPair;
/**
* Generate random KeyPair using OpenSSL.
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class KeyPairGenerator
{
private $generator;
public function __construct(PrivateKeyGeneratorInterface $generator = null)
{
$this->generator = $generator ?: new ChainPrivateKeyGenerator(
[
new RsaKeyGenerator(),
new EcKeyGenerator(),
new DhKeyGenerator(),
new DsaKeyGenerator(),
]
);
}
/**
* @param KeyOption|null $keyOption configuration of the key to generate
*
* @throws KeyPairGenerationException when OpenSSL failed to generate keys
*/
public function generateKeyPair(KeyOption $keyOption = null): KeyPair
{
if (null === $keyOption) {
$keyOption = new RsaKeyOption();
}
try {
$privateKey = $this->generator->generatePrivateKey($keyOption);
} catch (KeyGenerationException $e) {
throw new KeyPairGenerationException('Fail to generate a KeyPair with the given options', 0, $e);
}
return new KeyPair(
$privateKey->getPublicKey(),
$privateKey
);
}
}
@@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Generator;
use AcmePhp\Ssl\Exception\KeyGenerationException;
use AcmePhp\Ssl\Exception\KeyPairGenerationException;
use AcmePhp\Ssl\PrivateKey;
trait OpensslPrivateKeyGeneratorTrait
{
private function generatePrivateKeyFromOpensslOptions(array $opensslOptions): PrivateKey
{
$resource = openssl_pkey_new($opensslOptions);
if (!$resource) {
throw new KeyGenerationException(sprintf('OpenSSL key creation failed during generation with error: %s', openssl_error_string()));
}
if (!openssl_pkey_export($resource, $privateKey)) {
throw new KeyPairGenerationException(sprintf('OpenSSL key export failed during generation with error: %s', openssl_error_string()));
}
// PHP 8 automatically frees the key instance and deprecates the function
if (\PHP_VERSION_ID < 80000) {
openssl_free_key($resource);
}
return new PrivateKey($privateKey);
}
}
@@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Generator;
use AcmePhp\Ssl\Exception\KeyGenerationException;
use AcmePhp\Ssl\PrivateKey;
/**
* Generate random private key.
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
interface PrivateKeyGeneratorInterface
{
/**
* Generate a PrivateKey.
*
* @param KeyOption $keyOption configuration of the key to generate
*
* @throws KeyGenerationException when OpenSSL failed to generate keys
*/
public function generatePrivateKey(KeyOption $keyOption): PrivateKey;
/**
* Returns whether the instance is able to generator a private key from the given option.
*
* @param KeyOption $keyOption configuration of the key to generate
*/
public function supportsKeyOption(KeyOption $keyOption): bool;
}
@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Generator\RsaKey;
use AcmePhp\Ssl\Generator\KeyOption;
use AcmePhp\Ssl\Generator\OpensslPrivateKeyGeneratorTrait;
use AcmePhp\Ssl\Generator\PrivateKeyGeneratorInterface;
use AcmePhp\Ssl\PrivateKey;
use Webmozart\Assert\Assert;
/**
* Generate random RSA private key using OpenSSL.
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class RsaKeyGenerator implements PrivateKeyGeneratorInterface
{
use OpensslPrivateKeyGeneratorTrait;
public function generatePrivateKey(KeyOption $keyOption): PrivateKey
{
Assert::isInstanceOf($keyOption, RsaKeyOption::class);
return $this->generatePrivateKeyFromOpensslOptions([
'private_key_type' => OPENSSL_KEYTYPE_RSA,
'private_key_bits' => $keyOption->getBits(),
]);
}
public function supportsKeyOption(KeyOption $keyOption): bool
{
return $keyOption instanceof RsaKeyOption;
}
}
@@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Generator\RsaKey;
use AcmePhp\Ssl\Generator\KeyOption;
class RsaKeyOption implements KeyOption
{
/** @var int */
private $bits;
public function __construct(int $bits = 4096)
{
$this->bits = $bits;
}
public function getBits(): int
{
return $this->bits;
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl;
use Webmozart\Assert\Assert;
/**
* Represent a SSL key.
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
abstract class Key
{
/** @var string */
protected $keyPEM;
public function __construct(string $keyPEM)
{
Assert::stringNotEmpty($keyPEM, __CLASS__.'::$keyPEM should not be an empty string. Got %s');
$this->keyPEM = $keyPEM;
}
public function getPEM(): string
{
return $this->keyPEM;
}
public function getDER(): string
{
$lines = explode("\n", trim($this->keyPEM));
unset($lines[\count($lines) - 1]);
unset($lines[0]);
$result = implode('', $lines);
$result = base64_decode($result);
return $result;
}
/**
* @return resource|\OpenSSLAsymmetricKey
*/
abstract public function getResource();
}
+42
View File
@@ -0,0 +1,42 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl;
/**
* Represent a SSL key-pair (public and private).
*
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
class KeyPair
{
/** @var PublicKey */
private $publicKey;
/** @var PrivateKey */
private $privateKey;
public function __construct(PublicKey $publicKey, PrivateKey $privateKey)
{
$this->publicKey = $publicKey;
$this->privateKey = $privateKey;
}
public function getPublicKey(): PublicKey
{
return $this->publicKey;
}
public function getPrivateKey(): PrivateKey
{
return $this->privateKey;
}
}
+22
View File
@@ -0,0 +1,22 @@
Copyright (c) 2016 Titouan Galopin
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
+123
View File
@@ -0,0 +1,123 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl;
use Webmozart\Assert\Assert;
/**
* Represent the content of a parsed certificate.
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class ParsedCertificate
{
/** @var Certificate */
private $source;
/** @var string */
private $subject;
/** @var string */
private $issuer;
/** @var bool */
private $selfSigned;
/** @var \DateTime */
private $validFrom;
/** @var \DateTime */
private $validTo;
/** @var string */
private $serialNumber;
/** @var array */
private $subjectAlternativeNames;
/**
* @param string $issuer
* @param \DateTime $validFrom
* @param \DateTime $validTo
* @param string $serialNumber
*/
public function __construct(
Certificate $source,
string $subject,
string $issuer = null,
bool $selfSigned = true,
\DateTime $validFrom = null,
\DateTime $validTo = null,
string $serialNumber = null,
array $subjectAlternativeNames = []
) {
Assert::stringNotEmpty($subject, __CLASS__.'::$subject expected a non empty string. Got: %s');
Assert::allStringNotEmpty(
$subjectAlternativeNames,
__CLASS__.'::$subjectAlternativeNames expected a array of non empty string. Got: %s'
);
$this->source = $source;
$this->subject = $subject;
$this->issuer = $issuer;
$this->selfSigned = $selfSigned;
$this->validFrom = $validFrom;
$this->validTo = $validTo;
$this->serialNumber = $serialNumber;
$this->subjectAlternativeNames = $subjectAlternativeNames;
}
public function getSource(): Certificate
{
return $this->source;
}
public function getSubject(): string
{
return $this->subject;
}
public function getIssuer(): ?string
{
return $this->issuer;
}
public function isSelfSigned(): bool
{
return $this->selfSigned;
}
public function getValidFrom(): \DateTimeInterface
{
return $this->validFrom;
}
public function getValidTo(): \DateTimeInterface
{
return $this->validTo;
}
public function isExpired(): bool
{
return $this->validTo < (new \DateTime());
}
public function getSerialNumber(): ?string
{
return $this->serialNumber;
}
public function getSubjectAlternativeNames(): array
{
return $this->subjectAlternativeNames;
}
}
+92
View File
@@ -0,0 +1,92 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl;
use Webmozart\Assert\Assert;
/**
* Represent the content of a parsed key.
*
* @see openssl_pkey_get_details
*
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
class ParsedKey
{
/** @var Key */
private $source;
/** @var string */
private $key;
/** @var int */
private $bits;
/** @var int */
private $type;
/** @var array */
private $details;
public function __construct(Key $source, string $key, int $bits, int $type, array $details = [])
{
Assert::stringNotEmpty($key, __CLASS__.'::$key expected a non empty string. Got: %s');
Assert::oneOf(
$type,
[OPENSSL_KEYTYPE_RSA, OPENSSL_KEYTYPE_DSA, OPENSSL_KEYTYPE_DH, OPENSSL_KEYTYPE_EC],
__CLASS__.'::$type expected one of: %2$s. Got: %s'
);
$this->source = $source;
$this->key = $key;
$this->bits = $bits;
$this->type = $type;
$this->details = $details;
}
public function getSource(): Key
{
return $this->source;
}
public function getKey(): string
{
return $this->key;
}
public function getBits(): int
{
return $this->bits;
}
public function getType(): int
{
return $this->type;
}
public function getDetails(): array
{
return $this->details;
}
public function hasDetail(string $name): bool
{
return isset($this->details[$name]);
}
public function getDetail(string $name)
{
Assert::oneOf($name, array_keys($this->details), 'ParsedKey::getDetail() expected one of: %2$s. Got: %s');
return $this->details[$name];
}
}
+76
View File
@@ -0,0 +1,76 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Parser;
use AcmePhp\Ssl\Certificate;
use AcmePhp\Ssl\Exception\CertificateParsingException;
use AcmePhp\Ssl\ParsedCertificate;
/**
* Parse certificate to extract metadata.
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class CertificateParser
{
public function parse(Certificate $certificate): ParsedCertificate
{
$rawData = openssl_x509_parse($certificate->getPEM());
if (!\is_array($rawData)) {
throw new CertificateParsingException(sprintf('Fail to parse certificate with error: %s', openssl_error_string()));
}
if (!isset($rawData['subject']['CN'])) {
throw new CertificateParsingException('Missing expected key "subject.cn" in certificate');
}
if (!isset($rawData['serialNumber'])) {
throw new CertificateParsingException('Missing expected key "serialNumber" in certificate');
}
if (!isset($rawData['validFrom_time_t'])) {
throw new CertificateParsingException('Missing expected key "validFrom_time_t" in certificate');
}
if (!isset($rawData['validTo_time_t'])) {
throw new CertificateParsingException('Missing expected key "validTo_time_t" in certificate');
}
$subjectAlternativeName = [];
if (isset($rawData['extensions']['subjectAltName'])) {
$subjectAlternativeName = array_map(
function ($item) {
return explode(':', trim($item), 2)[1];
},
array_filter(
explode(
',',
$rawData['extensions']['subjectAltName']
),
function ($item) {
return false !== strpos($item, ':');
}
)
);
}
return new ParsedCertificate(
$certificate,
$rawData['subject']['CN'],
isset($rawData['issuer']['CN']) ? $rawData['issuer']['CN'] : null,
$rawData['subject'] === $rawData['issuer'],
new \DateTime('@'.$rawData['validFrom_time_t']),
new \DateTime('@'.$rawData['validTo_time_t']),
$rawData['serialNumber'],
$subjectAlternativeName
);
}
}
+65
View File
@@ -0,0 +1,65 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Parser;
use AcmePhp\Ssl\Exception\KeyFormatException;
use AcmePhp\Ssl\Exception\KeyParsingException;
use AcmePhp\Ssl\Key;
use AcmePhp\Ssl\ParsedKey;
/**
* Parse keys to extract metadata.
*
* @author Titouan Galopin
*/
class KeyParser
{
public function parse(Key $key): ParsedKey
{
try {
$resource = $key->getResource();
} catch (KeyFormatException $e) {
throw new KeyParsingException('Fail to load resource for key', 0, $e);
}
$rawData = openssl_pkey_get_details($resource);
// PHP 8 automatically frees the key instance and deprecates the function
if (\PHP_VERSION_ID < 80000) {
openssl_free_key($resource);
}
if (!\is_array($rawData)) {
throw new KeyParsingException(sprintf('Fail to parse key with error: %s', openssl_error_string()));
}
foreach (['type', 'key', 'bits'] as $requiredKey) {
if (!isset($rawData[$requiredKey])) {
throw new KeyParsingException(sprintf('Missing expected key "%s" in OpenSSL key', $requiredKey));
}
}
$details = [];
if (OPENSSL_KEYTYPE_RSA === $rawData['type']) {
$details = $rawData['rsa'];
} elseif (OPENSSL_KEYTYPE_DSA === $rawData['type']) {
$details = $rawData['dsa'];
} elseif (OPENSSL_KEYTYPE_DH === $rawData['type']) {
$details = $rawData['dh'];
} elseif (OPENSSL_KEYTYPE_EC === $rawData['type']) {
$details = $rawData['ec'];
}
return new ParsedKey($key, $rawData['key'], $rawData['bits'], $rawData['type'], $details);
}
}
+63
View File
@@ -0,0 +1,63 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl;
use AcmePhp\Ssl\Exception\KeyFormatException;
use Webmozart\Assert\Assert;
/**
* Represent a SSL Private key.
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class PrivateKey extends Key
{
/**
* {@inheritdoc}
*/
public function getResource()
{
if (!$resource = openssl_pkey_get_private($this->keyPEM)) {
throw new KeyFormatException(sprintf('Failed to convert key into resource: %s', openssl_error_string()));
}
return $resource;
}
public function getPublicKey(): PublicKey
{
$resource = $this->getResource();
if (!$details = openssl_pkey_get_details($resource)) {
throw new KeyFormatException(sprintf('Failed to extract public key: %s', openssl_error_string()));
}
// PHP 8 automatically frees the key instance and deprecates the function
if (\PHP_VERSION_ID < 80000) {
openssl_free_key($resource);
}
return new PublicKey($details['key']);
}
public static function fromDER(string $keyDER): self
{
Assert::stringNotEmpty($keyDER, __METHOD__.'::$keyDER should be a non-empty string. Got %s');
$der = base64_encode($keyDER);
$lines = str_split($der, 65);
array_unshift($lines, '-----BEGIN PRIVATE KEY-----');
$lines[] = '-----END PRIVATE KEY-----';
$lines[] = '';
return new self(implode("\n", $lines));
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl;
use AcmePhp\Ssl\Exception\KeyFormatException;
use Webmozart\Assert\Assert;
/**
* Represent a SSL Public key.
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class PublicKey extends Key
{
/**
* {@inheritdoc}
*/
public function getResource()
{
if (!$resource = openssl_pkey_get_public($this->keyPEM)) {
throw new KeyFormatException(sprintf('Failed to convert key into resource: %s', openssl_error_string()));
}
return $resource;
}
public static function fromDER(string $keyDER): self
{
Assert::stringNotEmpty($keyDER, __METHOD__.'::$keyDER should be a non-empty string. Got %s');
$der = base64_encode($keyDER);
$lines = str_split($der, 65);
array_unshift($lines, '-----BEGIN PUBLIC KEY-----');
$lines[] = '-----END PUBLIC KEY-----';
$lines[] = '';
return new self(implode("\n", $lines));
}
public function getHPKP(): string
{
return base64_encode(hash('sha256', $this->getDER(), true));
}
}
+36
View File
@@ -0,0 +1,36 @@
Acme PHP SSL library
====================
[![Build Status](https://img.shields.io/travis/acmephp/acmephp/master.svg?style=flat-square)](https://travis-ci.org/acmephp/acmephp)
[![Quality Score](https://img.shields.io/scrutinizer/g/acmephp/acmephp.svg?style=flat-square)](https://scrutinizer-ci.com/g/acmephp/acmephp)
[![StyleCI](https://styleci.io/repos/59910490/shield)](https://styleci.io/repos/59910490)
[![Packagist Version](https://img.shields.io/packagist/v/acmephp/acmephp.svg?style=flat-square)](https://packagist.org/packages/acmephp/acmephp)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
Acme PHP SSL is a PHP wrapper around OpenSSL extension providing SSL encoding,
decoding, parsing and signing features.
It uses the recommended security settings and let you interact in a OOP
manner with SSL entities (public/private keys, certificates, ...).
> If you want to chat with us or have questions, ping
> @tgalopin or @jderusse on the [Symfony Slack](https://symfony.com/support)!
## Why use Acme PHP SSL?
Acme PHP SSL provides various useful tools solving different use-cases:
- generate public and private keys (see the `Generator\KeyPairGenerator`) ;
- sign data using a private key (see `Signer\DataSigner`) ;
- parse certificates to extract informations about them (see `Parser\CertificateParser`) ;
There are many more possible use-cases, don't hesitate to dig a bit deeper in the
documentation to find out if this library can solve your problem!
## Documentation
Read the official [Acme PHP SSL documentation](https://acmephp.github.io/acmephp/ssl/introduction.html).
## Launch the Test suite
The Acme PHP test suite is located in the main repository:
[https://github.com/acmephp/acmephp#launch-the-test-suite](https://github.com/acmephp/acmephp#launch-the-test-suite).
@@ -0,0 +1,130 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Signer;
use AcmePhp\Ssl\CertificateRequest;
use AcmePhp\Ssl\DistinguishedName;
use AcmePhp\Ssl\Exception\CSRSigningException;
/**
* Provide tools to sign certificate request.
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class CertificateRequestSigner
{
/**
* Generate a CSR from the given distinguishedName and keyPair.
*/
public function signCertificateRequest(CertificateRequest $certificateRequest): string
{
$csrObject = $this->createCsrWithSANsObject($certificateRequest);
if (!$csrObject || !openssl_csr_export($csrObject, $csrExport)) {
throw new CSRSigningException(sprintf('OpenSSL CSR signing failed with error: %s', openssl_error_string()));
}
return $csrExport;
}
/**
* Generate a CSR object with SANs from the given distinguishedName and keyPair.
*/
protected function createCsrWithSANsObject(CertificateRequest $certificateRequest)
{
$sslConfigTemplate = <<<'EOL'
[ req ]
distinguished_name = req_distinguished_name
req_extensions = v3_req
[ req_distinguished_name ]
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @req_subject_alt_name
[ req_subject_alt_name ]
%s
EOL;
$sslConfigDomains = [];
$distinguishedName = $certificateRequest->getDistinguishedName();
$domains = array_merge(
[$distinguishedName->getCommonName()],
$distinguishedName->getSubjectAlternativeNames()
);
foreach (array_values($domains) as $index => $domain) {
$sslConfigDomains[] = 'DNS.'.($index + 1).' = '.$domain;
}
$sslConfigContent = sprintf($sslConfigTemplate, implode("\n", $sslConfigDomains));
$sslConfigFile = tempnam(sys_get_temp_dir(), 'acmephp_');
try {
file_put_contents($sslConfigFile, $sslConfigContent);
$resource = $certificateRequest->getKeyPair()->getPrivateKey()->getResource();
$csr = openssl_csr_new(
$this->getCSRPayload($distinguishedName),
$resource,
[
'digest_alg' => 'sha256',
'config' => $sslConfigFile,
]
);
// PHP 8 automatically frees the key instance and deprecates the function
if (\PHP_VERSION_ID < 80000) {
openssl_free_key($resource);
}
if (!$csr) {
throw new CSRSigningException(sprintf('OpenSSL CSR signing failed with error: %s', openssl_error_string()));
}
return $csr;
} finally {
unlink($sslConfigFile);
}
}
/**
* Retrieves a CSR payload from the given distinguished name.
*/
private function getCSRPayload(DistinguishedName $distinguishedName): array
{
$payload = [];
if (null !== $countryName = $distinguishedName->getCountryName()) {
$payload['countryName'] = $countryName;
}
if (null !== $stateOrProvinceName = $distinguishedName->getStateOrProvinceName()) {
$payload['stateOrProvinceName'] = $stateOrProvinceName;
}
if (null !== $localityName = $distinguishedName->getLocalityName()) {
$payload['localityName'] = $localityName;
}
if (null !== $OrganizationName = $distinguishedName->getOrganizationName()) {
$payload['organizationName'] = $OrganizationName;
}
if (null !== $organizationUnitName = $distinguishedName->getOrganizationalUnitName()) {
$payload['organizationalUnitName'] = $organizationUnitName;
}
if (null !== $commonName = $distinguishedName->getCommonName()) {
$payload['commonName'] = $commonName;
}
if (null !== $emailAddress = $distinguishedName->getEmailAddress()) {
$payload['emailAddress'] = $emailAddress;
}
return $payload;
}
}
+118
View File
@@ -0,0 +1,118 @@
<?php
/*
* This file is part of the Acme PHP project.
*
* (c) Titouan Galopin <galopintitouan@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace AcmePhp\Ssl\Signer;
use AcmePhp\Ssl\Exception\DataSigningException;
use AcmePhp\Ssl\PrivateKey;
use Webmozart\Assert\Assert;
/**
* Provide tools to sign data using a private key.
*
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
class DataSigner
{
public const FORMAT_DER = 'DER';
public const FORMAT_ECDSA = 'ECDSA';
/**
* Generate a signature of the given data using a private key and an algorithm.
*
* @param string $data Data to sign
* @param PrivateKey $privateKey Key used to sign
* @param int $algorithm Signature algorithm defined by constants OPENSSL_ALGO_*
* @param string $format Format of the output
*/
public function signData(string $data, PrivateKey $privateKey, int $algorithm = OPENSSL_ALGO_SHA256, string $format = self::FORMAT_DER): string
{
Assert::oneOf($format, [self::FORMAT_ECDSA, self::FORMAT_DER], 'The format %s to sign request does not exists. Available format: %s');
$resource = $privateKey->getResource();
if (!openssl_sign($data, $signature, $resource, $algorithm)) {
throw new DataSigningException(sprintf('OpenSSL data signing failed with error: %s', openssl_error_string()));
}
// PHP 8 automatically frees the key instance and deprecates the function
if (\PHP_VERSION_ID < 80000) {
openssl_free_key($resource);
}
switch ($format) {
case self::FORMAT_DER:
return $signature;
case self::FORMAT_ECDSA:
switch ($algorithm) {
case OPENSSL_ALGO_SHA256:
return $this->DERtoECDSA($signature, 64);
case OPENSSL_ALGO_SHA384:
return $this->DERtoECDSA($signature, 96);
case OPENSSL_ALGO_SHA512:
return $this->DERtoECDSA($signature, 132);
}
throw new DataSigningException('Unable to generate a ECDSA signature with the given algorithm');
default:
throw new DataSigningException('The given format does exists');
}
}
/**
* Convert a DER signature into ECDSA.
*
* The code is a copy/paste from another lib (web-token/jwt-core) which is not compatible with php <= 7.0
*
* @see https://github.com/web-token/jwt-core/blob/master/Util/ECSignature.php
*/
private function DERtoECDSA($der, $partLength): string
{
$hex = unpack('H*', $der)[1];
if ('30' !== mb_substr($hex, 0, 2, '8bit')) { // SEQUENCE
throw new DataSigningException('Invalid signature provided');
}
if ('81' === mb_substr($hex, 2, 2, '8bit')) { // LENGTH > 128
$hex = mb_substr($hex, 6, null, '8bit');
} else {
$hex = mb_substr($hex, 4, null, '8bit');
}
if ('02' !== mb_substr($hex, 0, 2, '8bit')) { // INTEGER
throw new DataSigningException('Invalid signature provided');
}
$Rl = hexdec(mb_substr($hex, 2, 2, '8bit'));
$R = $this->retrievePositiveInteger(mb_substr($hex, 4, $Rl * 2, '8bit'));
$R = str_pad($R, $partLength, '0', STR_PAD_LEFT);
$hex = mb_substr($hex, 4 + $Rl * 2, null, '8bit');
if ('02' !== mb_substr($hex, 0, 2, '8bit')) { // INTEGER
throw new DataSigningException('Invalid signature provided');
}
$Sl = hexdec(mb_substr($hex, 2, 2, '8bit'));
$S = $this->retrievePositiveInteger(mb_substr($hex, 4, $Sl * 2, '8bit'));
$S = str_pad($S, $partLength, '0', STR_PAD_LEFT);
return pack('H*', $R.$S);
}
/**
* The code is a copy/paste from another lib (web-token/jwt-core) which is not compatible with php <= 7.0.
*
* @see https://github.com/web-token/jwt-core/blob/master/Util/ECSignature.php
*/
private function retrievePositiveInteger($data)
{
while ('00' === mb_substr($data, 0, 2, '8bit') && mb_substr($data, 2, 2, '8bit') > '7f') {
$data = mb_substr($data, 2, null, '8bit');
}
return $data;
}
}
+44
View File
@@ -0,0 +1,44 @@
{
"name": "acmephp/ssl",
"description": "PHP wrapper around OpenSSL extension providing SSL encoding, decoding, parsing and signing features",
"type": "library",
"license": "MIT",
"homepage": "https://github.com/acmephp/ssl",
"keywords": [
"certificate",
"https",
"acmephp",
"ssl",
"RSA",
"ECDSA",
"openssl",
"CSR",
"x509"
],
"authors": [
{
"name": "Titouan Galopin",
"email": "galopintitouan@gmail.com",
"homepage": "http://titouangalopin.com"
},
{
"name": "Jérémy Derussé",
"homepage": "https://twitter.com/jderusse"
}
],
"autoload": {
"psr-4": {
"AcmePhp\\Ssl\\": "."
}
},
"require": {
"php": ">=7.2.5",
"ext-hash": "*",
"ext-openssl": "*",
"lib-openssl": ">=0.9.8",
"webmozart/assert": "^1.0"
},
"config": {
"sort-packages": true
}
}