1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace JWX\JWS\Algorithm; |
4
|
|
|
|
5
|
|
|
use CryptoUtil\ASN1\EC\ECDSASigValue; |
6
|
|
|
use CryptoUtil\Conversion\ECConversion; |
7
|
|
|
use JWX\JWA\JWA; |
8
|
|
|
use JWX\JWK\EC\ECPrivateKeyJWK; |
9
|
|
|
use JWX\JWK\EC\ECPublicKeyJWK; |
10
|
|
|
use JWX\JWK\JWK; |
11
|
|
|
use JWX\JWK\Parameter\CurveParameter; |
12
|
|
|
use JWX\JWT\Header\Header; |
13
|
|
|
use JWX\JWT\Parameter\AlgorithmParameter; |
14
|
|
|
use JWX\JWT\Parameter\JWTParameter; |
15
|
|
|
|
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* Base class for algorithms implementing elliptic curve signature computation. |
19
|
|
|
* |
20
|
|
|
* @link https://tools.ietf.org/html/rfc7518#section-3.4 |
21
|
|
|
*/ |
22
|
|
|
abstract class ECDSAAlgorithm extends OpenSSLSignatureAlgorithm |
23
|
|
|
{ |
24
|
|
|
/** |
25
|
|
|
* Mapping from algorithm name to class name. |
26
|
|
|
* |
27
|
|
|
* @internal |
28
|
|
|
* |
29
|
|
|
* @var array |
30
|
|
|
*/ |
31
|
|
|
const MAP_ALGO_TO_CLASS = array( |
32
|
|
|
/* @formatter:off */ |
33
|
|
|
JWA::ALGO_ES256 => ES256Algorithm::class, |
34
|
|
|
JWA::ALGO_ES384 => ES384Algorithm::class, |
35
|
|
|
JWA::ALGO_ES512 => ES512Algorithm::class |
36
|
|
|
/* @formatter:on */ |
37
|
|
|
); |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* Signature size in bytes. |
41
|
|
|
* |
42
|
|
|
* @var int |
43
|
|
|
*/ |
44
|
|
|
private $_signatureSize; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Get the name of the curve used by this algorithm. |
48
|
|
|
* |
49
|
|
|
* @return string |
50
|
|
|
*/ |
51
|
|
|
abstract protected function _curveName(); |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* Constructor |
55
|
|
|
* |
56
|
|
|
* @param ECPublicKeyJWK $pub_key |
57
|
|
|
* @param ECPrivateKeyJWK $priv_key |
58
|
|
|
*/ |
59
|
10 |
|
protected function __construct(ECPublicKeyJWK $pub_key, |
60
|
|
|
ECPrivateKeyJWK $priv_key = null) { |
61
|
10 |
|
$curve = $pub_key->curveParameter()->value(); |
62
|
10 |
|
if ($this->_curveName() != $curve) { |
63
|
1 |
|
throw new \InvalidArgumentException( |
64
|
1 |
|
"Key with " . $this->_curveName() . |
65
|
1 |
|
" curve expected, got $curve."); |
66
|
|
|
} |
67
|
9 |
|
$this->_publicKey = $pub_key; |
68
|
9 |
|
$this->_privateKey = $priv_key; |
69
|
9 |
|
$key_size = $pub_key->curveParameter()->keySizeBits(); |
70
|
9 |
|
$this->_signatureSize = intval(ceil($key_size / 8) * 2); |
71
|
9 |
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Initialize from a public key. |
75
|
|
|
* |
76
|
|
|
* @param ECPublicKeyJWK $jwk |
77
|
|
|
* @return self |
78
|
|
|
*/ |
79
|
4 |
|
public static function fromPublicKey(ECPublicKeyJWK $jwk) { |
80
|
4 |
|
return new static($jwk); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* Initialize from a private key. |
85
|
|
|
* |
86
|
|
|
* @param ECPrivateKeyJWK $jwk |
87
|
|
|
* @return self |
88
|
|
|
*/ |
89
|
6 |
|
public static function fromPrivateKey(ECPrivateKeyJWK $jwk) { |
90
|
6 |
|
return new static($jwk->publicKey(), $jwk); |
91
|
|
|
} |
92
|
|
|
|
93
|
6 |
View Code Duplication |
public static function fromJWK(JWK $jwk, Header $header) { |
|
|
|
|
94
|
6 |
|
$alg = JWA::deriveAlgorithmName($header, $jwk); |
95
|
6 |
|
if (!array_key_exists($alg, self::MAP_ALGO_TO_CLASS)) { |
96
|
1 |
|
throw new \UnexpectedValueException("Unsupported algorithm '$alg'."); |
97
|
|
|
} |
98
|
5 |
|
$cls = self::MAP_ALGO_TO_CLASS[$alg]; |
99
|
5 |
|
if ($jwk->has(...ECPrivateKeyJWK::MANAGED_PARAMS)) { |
100
|
3 |
|
return $cls::fromPrivateKey(ECPrivateKeyJWK::fromJWK($jwk)); |
101
|
|
|
} |
102
|
2 |
|
return $cls::fromPublicKey(ECPublicKeyJWK::fromJWK($jwk)); |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* |
107
|
|
|
* @see \JWX\JWS\Algorithm\OpenSSLSignatureAlgorithm::computeSignature() |
108
|
|
|
* @return string |
109
|
|
|
*/ |
110
|
4 |
|
public function computeSignature($data) { |
111
|
|
|
// OpenSSL returns ECDSA signature as a DER encoded ECDSA-Sig-Value |
112
|
4 |
|
$der = parent::computeSignature($data); |
113
|
4 |
|
$sig = ECDSASigValue::fromDER($der); |
114
|
4 |
|
$mlen = intval(floor($this->_signatureSize / 2)); |
115
|
4 |
|
$signature = ECConversion::numberToOctets($sig->r(), $mlen) . |
116
|
4 |
|
ECConversion::numberToOctets($sig->s(), $mlen); |
117
|
4 |
|
return $signature; |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* |
122
|
|
|
* @see \JWX\JWS\Algorithm\OpenSSLSignatureAlgorithm::validateSignature() |
123
|
|
|
* @return bool |
124
|
|
|
*/ |
125
|
5 |
|
public function validateSignature($data, $signature) { |
126
|
5 |
|
if (strlen($signature) != $this->_signatureSize) { |
127
|
1 |
|
throw new \UnexpectedValueException("Invalid signature length."); |
128
|
|
|
} |
129
|
4 |
|
list($r_octets, $s_octets) = str_split($signature, |
130
|
4 |
|
intval(floor($this->_signatureSize / 2))); |
131
|
|
|
// convert signature to DER sequence for OpenSSL |
132
|
4 |
|
$r = ECConversion::octetsToNumber($r_octets); |
133
|
4 |
|
$s = ECConversion::octetsToNumber($s_octets); |
134
|
4 |
|
$sig = new ECDSASigValue($r, $s); |
135
|
4 |
|
return parent::validateSignature($data, $sig->toDER()); |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* |
140
|
|
|
* @see \JWX\JWT\Header\HeaderParameters::headerParameters() |
141
|
|
|
* @return JWTParameter[] |
142
|
|
|
*/ |
143
|
2 |
|
public function headerParameters() { |
144
|
2 |
|
return array(AlgorithmParameter::fromAlgorithm($this)); |
145
|
|
|
} |
146
|
|
|
} |
147
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.