Subversion Repositories oidplus

Rev

Rev 846 | Rev 1042 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
827 daniel-mar 1
<?php
2
 
3
/**
4
 * Pure-PHP FIPS 186-4 compliant implementation of DSA.
5
 *
6
 * PHP version 5
7
 *
8
 * Here's an example of how to create signatures and verify signatures with this library:
9
 * <code>
10
 * <?php
11
 * include 'vendor/autoload.php';
12
 *
13
 * $private = \phpseclib3\Crypt\DSA::createKey();
14
 * $public = $private->getPublicKey();
15
 *
16
 * $plaintext = 'terrafrost';
17
 *
18
 * $signature = $private->sign($plaintext);
19
 *
20
 * echo $public->verify($plaintext, $signature) ? 'verified' : 'unverified';
21
 * ?>
22
 * </code>
23
 *
874 daniel-mar 24
 * @category  Crypt
25
 * @package   DSA
827 daniel-mar 26
 * @author    Jim Wigginton <terrafrost@php.net>
27
 * @copyright 2016 Jim Wigginton
28
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
29
 * @link      http://phpseclib.sourceforge.net
30
 */
31
 
32
namespace phpseclib3\Crypt;
33
 
34
use phpseclib3\Crypt\Common\AsymmetricKey;
35
use phpseclib3\Crypt\DSA\Parameters;
36
use phpseclib3\Crypt\DSA\PrivateKey;
37
use phpseclib3\Crypt\DSA\PublicKey;
38
use phpseclib3\Exception\InsufficientSetupException;
39
use phpseclib3\Math\BigInteger;
40
 
41
/**
42
 * Pure-PHP FIPS 186-4 compliant implementation of DSA.
43
 *
874 daniel-mar 44
 * @package DSA
827 daniel-mar 45
 * @author  Jim Wigginton <terrafrost@php.net>
874 daniel-mar 46
 * @access  public
827 daniel-mar 47
 */
48
abstract class DSA extends AsymmetricKey
49
{
50
    /**
51
     * Algorithm Name
52
     *
53
     * @var string
874 daniel-mar 54
     * @access private
827 daniel-mar 55
     */
56
    const ALGORITHM = 'DSA';
57
 
58
    /**
59
     * DSA Prime P
60
     *
61
     * @var \phpseclib3\Math\BigInteger
874 daniel-mar 62
     * @access private
827 daniel-mar 63
     */
64
    protected $p;
65
 
66
    /**
67
     * DSA Group Order q
68
     *
69
     * Prime divisor of p-1
70
     *
71
     * @var \phpseclib3\Math\BigInteger
874 daniel-mar 72
     * @access private
827 daniel-mar 73
     */
74
    protected $q;
75
 
76
    /**
77
     * DSA Group Generator G
78
     *
79
     * @var \phpseclib3\Math\BigInteger
874 daniel-mar 80
     * @access private
827 daniel-mar 81
     */
82
    protected $g;
83
 
84
    /**
85
     * DSA public key value y
86
     *
87
     * @var \phpseclib3\Math\BigInteger
874 daniel-mar 88
     * @access private
827 daniel-mar 89
     */
90
    protected $y;
91
 
92
    /**
93
     * Signature Format
94
     *
95
     * @var string
874 daniel-mar 96
     * @access private
827 daniel-mar 97
     */
98
    protected $sigFormat;
99
 
100
    /**
101
     * Signature Format (Short)
102
     *
103
     * @var string
874 daniel-mar 104
     * @access private
827 daniel-mar 105
     */
106
    protected $shortFormat;
107
 
108
    /**
109
     * Create DSA parameters
110
     *
874 daniel-mar 111
     * @access public
827 daniel-mar 112
     * @param int $L
113
     * @param int $N
114
     * @return \phpseclib3\Crypt\DSA|bool
115
     */
116
    public static function createParameters($L = 2048, $N = 224)
117
    {
118
        self::initialize_static_variables();
119
 
120
        if (!isset(self::$engines['PHP'])) {
121
            self::useBestEngine();
122
        }
123
 
124
        switch (true) {
125
            case $N == 160:
126
            /*
127
              in FIPS 186-1 and 186-2 N was fixed at 160 whereas K had an upper bound of 1024.
128
              RFC 4253 (SSH Transport Layer Protocol) references FIPS 186-2 and as such most
129
              SSH DSA implementations only support keys with an N of 160.
130
              puttygen let's you set the size of L (but not the size of N) and uses 2048 as the
131
              default L value. that's not really compliant with any of the FIPS standards, however,
132
              for the purposes of maintaining compatibility with puttygen, we'll support it
133
            */
134
            //case ($L >= 512 || $L <= 1024) && (($L & 0x3F) == 0) && $N == 160:
135
            // FIPS 186-3 changed this as follows:
136
            //case $L == 1024 && $N == 160:
137
            case $L == 2048 && $N == 224:
138
            case $L == 2048 && $N == 256:
139
            case $L == 3072 && $N == 256:
140
                break;
141
            default:
142
                throw new \InvalidArgumentException('Invalid values for N and L');
143
        }
144
 
145
        $two = new BigInteger(2);
146
 
147
        $q = BigInteger::randomPrime($N);
148
        $divisor = $q->multiply($two);
149
 
150
        do {
151
            $x = BigInteger::random($L);
152
            list(, $c) = $x->divide($divisor);
153
            $p = $x->subtract($c->subtract(self::$one));
154
        } while ($p->getLength() != $L || !$p->isPrime());
155
 
156
        $p_1 = $p->subtract(self::$one);
157
        list($e) = $p_1->divide($q);
158
 
159
        // quoting http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf#page=50 ,
160
        // "h could be obtained from a random number generator or from a counter that
161
        //  changes after each use". PuTTY (sshdssg.c) starts h off at 1 and increments
162
        // it on each loop. wikipedia says "commonly h = 2 is used" so we'll just do that
163
        $h = clone $two;
164
        while (true) {
165
            $g = $h->powMod($e, $p);
166
            if (!$g->equals(self::$one)) {
167
                break;
168
            }
169
            $h = $h->add(self::$one);
170
        }
171
 
172
        $dsa = new Parameters();
173
        $dsa->p = $p;
174
        $dsa->q = $q;
175
        $dsa->g = $g;
176
 
177
        return $dsa;
178
    }
179
 
180
    /**
181
     * Create public / private key pair.
182
     *
183
     * This method is a bit polymorphic. It can take a DSA/Parameters object, L / N as two distinct parameters or
184
     * no parameters (at which point L and N will be generated with this method)
185
     *
186
     * Returns the private key, from which the publickey can be extracted
187
     *
188
     * @param int[] ...$args
874 daniel-mar 189
     * @access public
827 daniel-mar 190
     * @return DSA\PrivateKey
191
     */
192
    public static function createKey(...$args)
193
    {
194
        self::initialize_static_variables();
195
 
196
        if (!isset(self::$engines['PHP'])) {
197
            self::useBestEngine();
198
        }
199
 
200
        if (count($args) == 2 && is_int($args[0]) && is_int($args[1])) {
201
            $params = self::createParameters($args[0], $args[1]);
202
        } elseif (count($args) == 1 && $args[0] instanceof Parameters) {
203
            $params = $args[0];
204
        } elseif (!count($args)) {
205
            $params = self::createParameters();
206
        } else {
207
            throw new InsufficientSetupException('Valid parameters are either two integers (L and N), a single DSA object or no parameters at all.');
208
        }
209
 
210
        $private = new PrivateKey();
211
        $private->p = $params->p;
212
        $private->q = $params->q;
213
        $private->g = $params->g;
214
 
215
        $private->x = BigInteger::randomRange(self::$one, $private->q->subtract(self::$one));
216
        $private->y = $private->g->powMod($private->x, $private->p);
217
 
218
        //$public = clone $private;
219
        //unset($public->x);
220
 
221
        return $private
222
            ->withHash($params->hash->getHash())
223
            ->withSignatureFormat($params->shortFormat);
224
    }
225
 
226
    /**
227
     * OnLoad Handler
228
     *
229
     * @return bool
874 daniel-mar 230
     * @access protected
827 daniel-mar 231
     * @param array $components
232
     */
233
    protected static function onLoad($components)
234
    {
235
        if (!isset(self::$engines['PHP'])) {
236
            self::useBestEngine();
237
        }
238
 
239
        if (!isset($components['x']) && !isset($components['y'])) {
240
            $new = new Parameters();
241
        } elseif (isset($components['x'])) {
242
            $new = new PrivateKey();
243
            $new->x = $components['x'];
244
        } else {
245
            $new = new PublicKey();
246
        }
247
 
248
        $new->p = $components['p'];
249
        $new->q = $components['q'];
250
        $new->g = $components['g'];
251
 
252
        if (isset($components['y'])) {
253
            $new->y = $components['y'];
254
        }
255
 
256
        return $new;
257
    }
258
 
259
    /**
260
     * Constructor
261
     *
262
     * PublicKey and PrivateKey objects can only be created from abstract RSA class
263
     */
264
    protected function __construct()
265
    {
266
        $this->sigFormat = self::validatePlugin('Signature', 'ASN1');
267
        $this->shortFormat = 'ASN1';
268
 
269
        parent::__construct();
270
    }
271
 
272
    /**
273
     * Returns the key size
274
     *
275
     * More specifically, this L (the length of DSA Prime P) and N (the length of DSA Group Order q)
276
     *
874 daniel-mar 277
     * @access public
827 daniel-mar 278
     * @return array
279
     */
280
    public function getLength()
281
    {
282
        return ['L' => $this->p->getLength(), 'N' => $this->q->getLength()];
283
    }
284
 
285
    /**
286
     * Returns the current engine being used
287
     *
288
     * @see self::useInternalEngine()
289
     * @see self::useBestEngine()
874 daniel-mar 290
     * @access public
827 daniel-mar 291
     * @return string
292
     */
293
    public function getEngine()
294
    {
295
        if (!isset(self::$engines['PHP'])) {
296
            self::useBestEngine();
297
        }
298
        return self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods()) ?
299
            'OpenSSL' : 'PHP';
300
    }
301
 
302
    /**
303
     * Returns the parameters
304
     *
305
     * A public / private key is only returned if the currently loaded "key" contains an x or y
306
     * value.
307
     *
308
     * @see self::getPublicKey()
874 daniel-mar 309
     * @access public
827 daniel-mar 310
     * @return mixed
311
     */
312
    public function getParameters()
313
    {
314
        $type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters');
315
 
316
        $key = $type::saveParameters($this->p, $this->q, $this->g);
317
        return DSA::load($key, 'PKCS1')
318
            ->withHash($this->hash->getHash())
319
            ->withSignatureFormat($this->shortFormat);
320
    }
321
 
322
    /**
323
     * Determines the signature padding mode
324
     *
325
     * Valid values are: ASN1, SSH2, Raw
326
     *
874 daniel-mar 327
     * @access public
827 daniel-mar 328
     * @param string $format
329
     */
330
    public function withSignatureFormat($format)
331
    {
332
        $new = clone $this;
333
        $new->shortFormat = $format;
334
        $new->sigFormat = self::validatePlugin('Signature', $format);
335
        return $new;
336
    }
337
 
338
    /**
339
     * Returns the signature format currently being used
340
     *
874 daniel-mar 341
     * @access public
827 daniel-mar 342
     */
343
    public function getSignatureFormat()
344
    {
345
        return $this->shortFormat;
346
    }
347
}