Failed Conditions
Push — v7 ( e9e51b...d9c0af )
by Florent
03:20
created

RSA::verifyEMSAPSS()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 23
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 8.5906
c 0
b 0
f 0
cc 6
eloc 20
nc 6
nop 4
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2014-2017 Spomky-Labs
9
 *
10
 * This software may be modified and distributed under the terms
11
 * of the MIT license.  See the LICENSE file for details.
12
 */
13
14
namespace Jose\Component\Signature\Util;
15
16
use Jose\Component\Core\Util\BigInteger;
17
use Jose\Component\KeyManagement\KeyConverter\RSAKey;
18
19
final class RSA
20
{
21
    /**
22
     * @param BigInteger $x
23
     * @param int        $xLen
24
     *
25
     * @return string
26
     */
27
    private static function convertIntegerToOctetString(BigInteger $x, int $xLen): string
28
    {
29
        $x = $x->toBytes();
30
        if (strlen($x) > $xLen) {
31
            throw new \RuntimeException();
32
        }
33
34
        return str_pad($x, $xLen, chr(0), STR_PAD_LEFT);
35
    }
36
37
    /**
38
     * Exponentiate with or without Chinese Remainder Theorem.
39
     * Operation with primes 'p' and 'q' is appox. 2x faster.
40
     *
41
     * @param RSAKey     $key
42
     * @param BigInteger $c
43
     *
44
     * @return BigInteger
45
     */
46
    private static function exponentiate(RSAKey $key, BigInteger $c): BigInteger
47
    {
48
        if ($c->compare(BigInteger::createFromDecimal(0)) < 0 || $c->compare($key->getModulus()) > 0) {
49
            throw new \RuntimeException();
50
        }
51
        if ($key->isPublic() || empty($key->getPrimes())) {
52
            return $c->modPow($key->getExponent(), $key->getModulus());
53
        }
54
55
        $p = $key->getPrimes()[0];
56
        $q = $key->getPrimes()[1];
57
        $dP = $key->getExponents()[0];
58
        $dQ = $key->getExponents()[1];
59
        $qInv = $key->getCoefficient();
60
61
        $m1 = $c->modPow($dP, $p);
62
        $m2 = $c->modPow($dQ, $q);
63
        $h = $qInv->multiply($m1->subtract($m2)->add($p))->mod($p);
64
        $m = $m2->add($h->multiply($q));
65
66
        return $m;
67
    }
68
69
    /**
70
     * MGF1.
71
     *
72
     * @param string $mgfSeed
73
     * @param int    $maskLen
74
     * @param Hash   $mgfHash
75
     *
76
     * @return string
77
     */
78
    private static function getMGF1(string $mgfSeed, int $maskLen, Hash $mgfHash): string
79
    {
80
        $t = '';
81
        $count = ceil($maskLen / $mgfHash->getLength());
82
        for ($i = 0; $i < $count; ++$i) {
83
            $c = pack('N', $i);
84
            $t .= $mgfHash->hash($mgfSeed.$c);
85
        }
86
87
        return mb_substr($t, 0, $maskLen, '8bit');
88
    }
89
90
    /**
91
     * EMSA-PSS-ENCODE.
92
     *
93
     * @param string $message
94
     * @param int    $modulusLength
95
     * @param Hash   $hash
96
     *
97
     * @return string
98
     */
99
    private static function encodeEMSAPSS(string $message, int $modulusLength, Hash $hash): string
100
    {
101
        $emLen = ($modulusLength + 1) >> 3;
102
        $sLen = $hash->getLength();
103
        $mHash = $hash->hash($message);
104
        if ($emLen <= $hash->getLength() + $sLen + 2) {
105
            throw new \RuntimeException();
106
        }
107
        $salt = random_bytes($sLen);
108
        $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
109
        $h = $hash->hash($m2);
110
        $ps = str_repeat(chr(0), $emLen - $sLen - $hash->getLength() - 2);
111
        $db = $ps.chr(1).$salt;
112
        $dbMask = self::getMGF1($h, $emLen - $hash->getLength() - 1, $hash);
113
        $maskedDB = $db ^ $dbMask;
114
        $maskedDB[0] = ~chr(0xFF << ($modulusLength & 7)) & $maskedDB[0];
115
        $em = $maskedDB.$h.chr(0xBC);
116
117
        return $em;
118
    }
119
120
    /**
121
     * EMSA-PSS-VERIFY.
122
     *
123
     * @param string $m
124
     * @param string $em
125
     * @param int    $emBits
126
     * @param Hash   $hash
127
     *
128
     * @return bool
129
     */
130
    private static function verifyEMSAPSS(string $m, string $em, int $emBits, Hash $hash): bool
131
    {
132
        $emLen = ($emBits + 1) >> 3;
133
        $sLen = $hash->getLength();
134
        $mHash = $hash->hash($m);
135
        if ($emLen < $hash->getLength() + $sLen + 2 ) {throw new \InvalidArgumentException();}
136
        if ($em[mb_strlen($em, '8bit') - 1] !== chr(0xBC)) {throw new \InvalidArgumentException();}
137
        $maskedDB = mb_substr($em, 0, -$hash->getLength() - 1, '8bit');
138
        $h = mb_substr($em, -$hash->getLength() - 1, $hash->getLength(), '8bit');
139
        $temp = chr(0xFF << ($emBits & 7));
140
        if ((~$maskedDB[0] & $temp) !== $temp) {throw new \InvalidArgumentException();}
141
        $dbMask = self::getMGF1($h, $emLen - $hash->getLength() - 1, $hash/*MGF*/);
142
        $db = $maskedDB ^ $dbMask;
143
        $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0];
144
        $temp = $emLen - $hash->getLength() - $sLen - 2;
145
        if (mb_substr($db, 0, $temp, '8bit') !== str_repeat(chr(0), $temp)) {throw new \InvalidArgumentException();}
146
        if (1 !== ord($db[$temp])) {throw new \InvalidArgumentException();}
147
        $salt = mb_substr($db, $temp + 1, null, '8bit'); // should be $sLen long
148
        $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt;
149
        $h2 = $hash->hash($m2);
150
151
        return hash_equals($h, $h2);
152
    }
153
154
    /**
155
     * Create a signature.
156
     *
157
     * @param RSAKey $key
158
     * @param string $message
159
     * @param string $hash
160
     *
161
     * @return string
162
     */
163
    public static function sign(RSAKey $key, string $message, string $hash): string
164
    {
165
        if (!in_array($hash, ['sha256', 'sha384', 'sha512'])) {
166
            throw new \InvalidArgumentException();
167
        }
168
        $em = self::encodeEMSAPSS($message, 8 * $key->getModulusLength() - 1, Hash::$hash());
169
        $message = BigInteger::createFromBinaryString($em);
170
        $signature = self::exponentiate($key, $message);
171
172
        return self::convertIntegerToOctetString($signature, $key->getModulusLength());
173
    }
174
175
    /**
176
     * Verifies a signature.
177
     *
178
     * @param RSAKey $key
179
     * @param string $message
180
     * @param string $signature
181
     * @param string $hash
182
     *
183
     * @return bool
184
     */
185
    public static function verify(RSAKey $key, string $message, string $signature, string $hash): bool
186
    {
187
        if (!in_array($hash, ['sha256', 'sha384', 'sha512'])) {
188
            throw new \InvalidArgumentException();
189
        }
190
        if (strlen($signature) !== $key->getModulusLength()) {
191
            throw new \InvalidArgumentException();
192
        }
193
        $modBits = 8 * $key->getModulusLength();
194
        $s2 = BigInteger::createFromBinaryString($signature);
195
        $m2 = self::exponentiate($key, $s2);
196
        $em = self::convertIntegerToOctetString($m2, $modBits >> 3);
197
198
        return self::verifyEMSAPSS($message, $em, $modBits - 1, Hash::$hash());
199
    }
200
}
201