Skip to content

Commit d34e9f5

Browse files
committed
Add support for ECDSA
1 parent 6acacbf commit d34e9f5

14 files changed

+494
-47
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Acme PHP project.
5+
*
6+
* (c) Titouan Galopin <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace AcmePhp\Ssl\Exception;
13+
14+
/**
15+
* @author Jérémy Derussé <[email protected]>
16+
*/
17+
class KeyGenerationException extends AcmeSslException
18+
{
19+
}

Exception/KeyPairGenerationException.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@
1414
/**
1515
* @author Jérémy Derussé <[email protected]>
1616
*/
17-
class KeyPairGenerationException extends AcmeSslException
17+
class KeyPairGenerationException extends KeyGenerationException
1818
{
1919
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Acme PHP project.
5+
*
6+
* (c) Titouan Galopin <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace AcmePhp\Ssl\Generator;
13+
14+
/**
15+
* Generate random RSA private key using OpenSSL.
16+
*
17+
* @author Jérémy Derussé <[email protected]>
18+
*/
19+
class ChainPrivateKeyGenerator implements PrivateKeyGeneratorInterface
20+
{
21+
/** @var PrivateKeyGeneratorInterface[] */
22+
private $generators;
23+
24+
/**
25+
* @param PrivateKeyGeneratorInterface[] $generators
26+
*/
27+
public function __construct($generators)
28+
{
29+
$this->generators = $generators;
30+
}
31+
32+
public function generatePrivateKey(KeyOption $keyOption)
33+
{
34+
foreach ($this->generators as $generator) {
35+
if ($generator->supportsKeyOption($keyOption)) {
36+
return $generator->generatePrivateKey($keyOption);
37+
}
38+
}
39+
40+
throw new \LogicException(
41+
sprintf('Unable to find a generator for a key option of type %s', \get_class($keyOption))
42+
);
43+
}
44+
45+
public function supportsKeyOption(KeyOption $keyOption)
46+
{
47+
foreach ($this->generators as $generator) {
48+
if ($generator->supportsKeyOption($keyOption)) {
49+
return true;
50+
}
51+
}
52+
53+
return false;
54+
}
55+
}

Generator/EcKey/EcKeyGenerator.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Acme PHP project.
5+
*
6+
* (c) Titouan Galopin <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace AcmePhp\Ssl\Generator\EcKey;
13+
14+
use AcmePhp\Ssl\Exception\KeyGenerationException;
15+
use AcmePhp\Ssl\Exception\KeyPairGenerationException;
16+
use AcmePhp\Ssl\Generator\KeyOption;
17+
use AcmePhp\Ssl\Generator\PrivateKeyGeneratorInterface;
18+
use AcmePhp\Ssl\PrivateKey;
19+
use Webmozart\Assert\Assert;
20+
21+
/**
22+
* Generate random EC private key using OpenSSL.
23+
*
24+
* @author Jérémy Derussé <[email protected]>
25+
*/
26+
class EcKeyGenerator implements PrivateKeyGeneratorInterface
27+
{
28+
/**
29+
* @param EcKeyOption|KeyOption $keyOption
30+
*/
31+
public function generatePrivateKey(KeyOption $keyOption)
32+
{
33+
Assert::isInstanceOf($keyOption, EcKeyOption::class);
34+
35+
$resource = openssl_pkey_new(
36+
[
37+
'private_key_type' => OPENSSL_KEYTYPE_EC,
38+
'curve_name' => $keyOption->getCurveName(),
39+
]
40+
);
41+
42+
if (!$resource) {
43+
throw new KeyGenerationException(
44+
sprintf('OpenSSL key creation failed during generation with error: %s', openssl_error_string())
45+
);
46+
}
47+
if (!openssl_pkey_export($resource, $privateKey)) {
48+
throw new KeyPairGenerationException(
49+
sprintf('OpenSSL key export failed during generation with error: %s', openssl_error_string())
50+
);
51+
}
52+
53+
return new PrivateKey($privateKey);
54+
}
55+
56+
public function supportsKeyOption(KeyOption $keyOption)
57+
{
58+
return $keyOption instanceof EcKeyOption;
59+
}
60+
}

Generator/EcKey/EcKeyOption.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Acme PHP project.
5+
*
6+
* (c) Titouan Galopin <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace AcmePhp\Ssl\Generator\EcKey;
13+
14+
use AcmePhp\Ssl\Generator\KeyOption;
15+
use Webmozart\Assert\Assert;
16+
17+
class EcKeyOption implements KeyOption
18+
{
19+
/** @var string */
20+
private $curveName;
21+
22+
public function __construct($curveName = 'secp384r1')
23+
{
24+
Assert::stringNotEmpty($curveName);
25+
Assert::oneOf($curveName, openssl_get_curve_names(), 'The given curve %s is not supported. Available curves are: %s');
26+
27+
$this->curveName = $curveName;
28+
}
29+
30+
/**
31+
* @return string
32+
*/
33+
public function getCurveName()
34+
{
35+
return $this->curveName;
36+
}
37+
}

Generator/KeyOption.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Acme PHP project.
5+
*
6+
* (c) Titouan Galopin <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace AcmePhp\Ssl\Generator;
13+
14+
interface KeyOption
15+
{
16+
}

Generator/KeyPairGenerator.php

Lines changed: 30 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111

1212
namespace AcmePhp\Ssl\Generator;
1313

14+
use AcmePhp\Ssl\Exception\KeyGenerationException;
1415
use AcmePhp\Ssl\Exception\KeyPairGenerationException;
16+
use AcmePhp\Ssl\Generator\EcKey\EcKeyGenerator;
17+
use AcmePhp\Ssl\Generator\RsaKey\RsaKeyGenerator;
18+
use AcmePhp\Ssl\Generator\RsaKey\RsaKeyOption;
1519
use AcmePhp\Ssl\KeyPair;
16-
use AcmePhp\Ssl\PrivateKey;
17-
use AcmePhp\Ssl\PublicKey;
1820
use Webmozart\Assert\Assert;
1921

2022
/**
@@ -24,58 +26,47 @@
2426
*/
2527
class KeyPairGenerator
2628
{
29+
private $generator;
30+
31+
public function __construct(PrivateKeyGeneratorInterface $generator = null)
32+
{
33+
$this->generator = $generator ?: new ChainPrivateKeyGenerator(
34+
[
35+
new RsaKeyGenerator(),
36+
new EcKeyGenerator(),
37+
]
38+
);
39+
}
40+
2741
/**
2842
* Generate KeyPair.
2943
*
30-
* @param int $keySize size of the key
44+
* @param KeyOption $keyOption configuration of the key to generate
3145
*
3246
* @throws KeyPairGenerationException when OpenSSL failed to generate keys
3347
*
3448
* @return KeyPair
3549
*/
36-
public function generateKeyPair($keySize = 4096)
50+
public function generateKeyPair($keyOption = null)
3751
{
38-
Assert::integer($keySize, __METHOD__.'::$keySize should be an integer. Got: %s');
39-
40-
$key = openssl_pkey_new(
41-
[
42-
'private_key_type' => OPENSSL_KEYTYPE_RSA,
43-
'private_key_bits' => $keySize,
44-
]
45-
);
46-
47-
if (!$key) {
48-
throw new KeyPairGenerationException(
49-
sprintf(
50-
'OpenSSL key creation failed during generation with error: %s',
51-
openssl_error_string()
52-
)
53-
);
52+
if (null === $keyOption) {
53+
$keyOption = new RsaKeyOption();
5454
}
55-
56-
if (!openssl_pkey_export($key, $privateKey)) {
57-
throw new KeyPairGenerationException(
58-
sprintf(
59-
'OpenSSL key export failed during generation with error: %s',
60-
openssl_error_string()
61-
)
62-
);
55+
if (\is_int($keyOption)) {
56+
@trigger_error('Passing a keySize to "generateKeyPair" is deprecated since version 1.1 and will be removed in 2.0. Pass an instance of KeyOption instead', E_USER_DEPRECATED);
57+
$keyOption = new RsaKeyOption($keyOption);
6358
}
59+
Assert::isInstanceOf($keyOption, KeyOption::class);
6460

65-
$details = openssl_pkey_get_details($key);
66-
67-
if (!\is_array($details)) {
68-
throw new KeyPairGenerationException(
69-
sprintf(
70-
'OpenSSL key parsing failed during generation with error: %s',
71-
openssl_error_string()
72-
)
73-
);
61+
try {
62+
$privateKey = $this->generator->generatePrivateKey($keyOption);
63+
} catch (KeyGenerationException $e) {
64+
throw new KeyPairGenerationException('Fail to generate a KeyPair with the given options', 0, $e);
7465
}
7566

7667
return new KeyPair(
77-
new PublicKey($details['key']),
78-
new PrivateKey($privateKey)
68+
$privateKey->getPublicKey(),
69+
$privateKey
7970
);
8071
}
8172
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Acme PHP project.
5+
*
6+
* (c) Titouan Galopin <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace AcmePhp\Ssl\Generator;
13+
14+
use AcmePhp\Ssl\Exception\KeyGenerationException;
15+
use AcmePhp\Ssl\PrivateKey;
16+
17+
/**
18+
* Generate random private key.
19+
*
20+
* @author Jérémy Derussé <[email protected]>
21+
*/
22+
interface PrivateKeyGeneratorInterface
23+
{
24+
/**
25+
* Generate a PrivateKey.
26+
*
27+
* @param KeyOption $keyOption configuration of the key to generate
28+
*
29+
* @throws KeyGenerationException when OpenSSL failed to generate keys
30+
*
31+
* @return PrivateKey
32+
*/
33+
public function generatePrivateKey(KeyOption $keyOption);
34+
35+
/**
36+
* Returns whether the instance is able to generator a private key from the given option.
37+
*
38+
* @param KeyOption $keyOption configuration of the key to generate
39+
*
40+
* @return bool
41+
*/
42+
public function supportsKeyOption(KeyOption $keyOption);
43+
}

0 commit comments

Comments
 (0)