Failed Conditions
Push — v7 ( 61ffea...f7e5f1 )
by Florent
04:20
created

RSA::convertOctetStringToInteger()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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