Completed
Push — master ( 1bb8ab...34d7f4 )
by Florent
05:19
created

ECDSA::convertHexToBin()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
/*
4
 * The MIT License (MIT)
5
 *
6
 * Copyright (c) 2014-2016 Spomky-Labs
7
 *
8
 * This software may be modified and distributed under the terms
9
 * of the MIT license.  See the LICENSE file for details.
10
 */
11
12
namespace Jose\Algorithm\Signature;
13
14
use Assert\Assertion;
15
use Base64Url\Base64Url;
16
use FG\ASN1\Object;
17
use FG\ASN1\Universal\Integer;
18
use FG\ASN1\Universal\Sequence;
19
use Jose\Algorithm\SignatureAlgorithmInterface;
20
use Jose\KeyConverter\ECKey;
21
use Jose\Object\JWKInterface;
22
use Mdanter\Ecc\Crypto\Signature\Signature;
23
use Mdanter\Ecc\EccFactory;
24
use Mdanter\Ecc\Random\RandomGeneratorFactory;
25
26
/**
27
 * Class ECDSA.
28
 */
29
abstract class ECDSA implements SignatureAlgorithmInterface
30
{
31
    /**
32
     * {@inheritdoc}
33
     */
34
    public function sign(JWKInterface $key, $data)
35
    {
36
        $this->checkKey($key);
37
        Assertion::true($key->has('d'), 'The EC key is not private');
38
39
        if (defined('OPENSSL_KEYTYPE_EC')) {
40
            return $this->getOpenSSLSignature($key, $data);
41
        }
42
43
        return $this->getPHPECCSignature($key, $data);
44
    }
45
46
    /**
47
     * @param \Jose\Object\JWKInterface $key
48
     * @param string                    $data
49
     *
50
     * @return string
51
     */
52
    private function getOpenSSLSignature(JWKInterface $key, $data)
53
    {
54
        $pem = (new ECKey($key))->toPEM();
55
        $result = openssl_sign($data, $signature, $pem, $this->getHashAlgorithm());
56
57
        Assertion::true($result, 'Signature failed');
58
59
        $asn = Object::fromBinary($signature);
60
        Assertion::isInstanceOf($asn, Sequence::class, 'Invalid signature');
61
62
        $res = '';
63
        foreach ($asn->getChildren() as $child) {
0 ignored issues
show
Bug introduced by
The method getChildren does only exist in FG\ASN1\Construct, but not in FG\ASN1\AbstractString a...d FG\ASN1\UnknownObject.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
64
            Assertion::isInstanceOf($child, Integer::class, 'Invalid signature');
65
            $res .= str_pad($this->convertDecToHex($child->getContent()), $this->getSignaturePartLength(), '0', STR_PAD_LEFT);
66
        }
67
68
        return $this->convertHexToBin($res);
69
    }
70
71
    /**
72
     * @param \Jose\Object\JWKInterface $key
73
     * @param string                    $data
74
     *
75
     * @return string
76
     */
77
    private function getPHPECCSignature(JWKInterface $key, $data)
78
    {
79
        $p = $this->getGenerator();
80
        $d = $this->convertBase64ToGmp($key->get('d'));
81
        $hash = $this->convertHexToGmp(hash($this->getHashAlgorithm(), $data));
82
83
        $k = RandomGeneratorFactory::getRandomGenerator()->generate($p->getOrder());
84
85
        $signer = EccFactory::getSigner();
86
87
        $private_key = $p->getPrivateKeyFrom($d);
88
        $signature = $signer->sign($private_key, $hash, $k);
89
90
        $part_length = $this->getSignaturePartLength();
91
92
        $R = str_pad($this->convertDecToHex($signature->getR()), $part_length, '0', STR_PAD_LEFT);
93
        $S = str_pad($this->convertDecToHex($signature->getS()), $part_length, '0', STR_PAD_LEFT);
94
95
        return $this->convertHexToBin($R.$S);
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     */
101
    public function verify(JWKInterface $key, $data, $signature)
102
    {
103
        $this->checkKey($key);
104
105
        $signature = $this->convertBinToHex($signature);
106
        $part_length = $this->getSignaturePartLength();
107
        if (mb_strlen($signature, '8bit') !== 2 * $part_length) {
108
            return false;
109
        }
110
        $R = mb_substr($signature, 0, $part_length, '8bit');
111
        $S = mb_substr($signature, $part_length, null, '8bit');
112
113
        if (defined('OPENSSL_KEYTYPE_EC')) {
114
            return $this->verifyOpenSSLSignature($key, $data, $R, $S);
115
        }
116
117
        return $this->verifyPHPECCSignature($key, $data, $R, $S);
118
    }
119
120
    /**
121
     * @param \Jose\Object\JWKInterface $key
122
     * @param string                    $data
123
     * @param string                    $R
124
     * @param string                    $S
125
     *
126
     * @return string
127
     */
128
    private function verifyOpenSSLSignature(JWKInterface $key, $data, $R, $S)
129
    {
130
        $pem = ECKey::toPublic(new ECKey($key))->toPEM();
131
132
        $oid_sequence = new Sequence();
133
        $oid_sequence->addChildren([
134
            new Integer(gmp_strval($this->convertHexToGmp($R), 10)),
135
            new Integer(gmp_strval($this->convertHexToGmp($S), 10)),
136
        ]);
137
138
        return 1 === openssl_verify($data, $oid_sequence->getBinary(), $pem, $this->getHashAlgorithm());
139
    }
140
141
    /**
142
     * @param \Jose\Object\JWKInterface $key
143
     * @param string                    $data
144
     * @param string                    $R
145
     * @param string                    $S
146
     *
147
     * @return string
148
     */
149
    private function verifyPHPECCSignature(JWKInterface $key, $data, $R, $S)
150
    {
151
        $p = $this->getGenerator();
152
        $x = $this->convertBase64ToGmp($key->get('x'));
153
        $y = $this->convertBase64ToGmp($key->get('y'));
154
        $hash = $this->convertHexToGmp(hash($this->getHashAlgorithm(), $data));
155
156
        $public_key = $p->getPublicKeyFrom($x, $y);
157
158
        $signer = EccFactory::getSigner();
159
160
        return $signer->verify(
161
            $public_key,
162
            new Signature(
163
                $this->convertHexToGmp($R),
164
                $this->convertHexToGmp($S)
165
            ),
166
            $hash
167
        );
168
    }
169
170
    /**
171
     * @return \Mdanter\Ecc\Primitives\GeneratorPoint
172
     */
173
    abstract protected function getGenerator();
174
175
    /**
176
     * @return string
177
     */
178
    abstract protected function getHashAlgorithm();
179
180
    /**
181
     * @return int
182
     */
183
    abstract protected function getSignaturePartLength();
184
185
    /**
186
     * @param string $value
187
     *
188
     * @return string
189
     */
190
    private function convertHexToBin($value)
191
    {
192
        return pack('H*', $value);
193
    }
194
195
    /**
196
     * @param string $value
197
     */
198
    private function convertBinToHex($value)
199
    {
200
        $value = unpack('H*', $value);
201
202
        return $value[1];
203
    }
204
205
    /**
206
     * @param $value
207
     *
208
     * @return string
209
     */
210
    private function convertDecToHex($value)
211
    {
212
        $value = gmp_strval($value, 10);
213
214
        return EccFactory::getAdapter()->decHex($value);
215
    }
216
217
    /**
218
     * @param $value
219
     *
220
     * @return \GMP
221
     */
222
    private function convertHexToGmp($value)
223
    {
224
        return gmp_init($value, 16);
225
    }
226
227
    /**
228
     * @param $value
229
     *
230
     * @return \GMP
231
     */
232
    private function convertBase64ToGmp($value)
233
    {
234
        $value = unpack('H*', Base64Url::decode($value));
235
236
        return gmp_init($value[1], 16);
237
    }
238
239
    /**
240
     * @param JWKInterface $key
241
     */
242
    private function checkKey(JWKInterface $key)
243
    {
244
        Assertion::eq($key->get('kty'), 'EC', 'Wrong key type.');
245
        Assertion::true($key->has('x'), 'The key parameter "x" is missing.');
246
        Assertion::true($key->has('y'), 'The key parameter "y" is missing.');
247
        Assertion::true($key->has('crv'), 'The key parameter "crv" is missing.');
248
    }
249
}
250