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 *
24 * @category  Crypt
25 * @package   DSA
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
32namespace phpseclib3\Crypt;
33
34use phpseclib3\Crypt\Common\AsymmetricKey;
35use phpseclib3\Crypt\DSA\Parameters;
36use phpseclib3\Crypt\DSA\PrivateKey;
37use phpseclib3\Crypt\DSA\PublicKey;
38use phpseclib3\Exception\InsufficientSetupException;
39use phpseclib3\Math\BigInteger;
40
41/**
42 * Pure-PHP FIPS 186-4 compliant implementation of DSA.
43 *
44 * @package DSA
45 * @author  Jim Wigginton <terrafrost@php.net>
46 * @access  public
47 */
48abstract class DSA extends AsymmetricKey
49{
50    /**
51     * Algorithm Name
52     *
53     * @var string
54     * @access private
55     */
56    const ALGORITHM = 'DSA';
57
58    /**
59     * DSA Prime P
60     *
61     * @var \phpseclib3\Math\BigInteger
62     * @access private
63     */
64    protected $p;
65
66    /**
67     * DSA Group Order q
68     *
69     * Prime divisor of p-1
70     *
71     * @var \phpseclib3\Math\BigInteger
72     * @access private
73     */
74    protected $q;
75
76    /**
77     * DSA Group Generator G
78     *
79     * @var \phpseclib3\Math\BigInteger
80     * @access private
81     */
82    protected $g;
83
84    /**
85     * DSA public key value y
86     *
87     * @var \phpseclib3\Math\BigInteger
88     * @access private
89     */
90    protected $y;
91
92    /**
93     * Signature Format
94     *
95     * @var string
96     * @access private
97     */
98    protected $sigFormat;
99
100    /**
101     * Signature Format (Short)
102     *
103     * @var string
104     * @access private
105     */
106    protected $shortFormat;
107
108    /**
109     * Create DSA parameters
110     *
111     * @access public
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
189     * @access public
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
230     * @access protected
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     *
277     * @access public
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()
290     * @access public
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()
309     * @access public
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     *
327     * @access public
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     *
341     * @access public
342     */
343    public function getSignatureFormat()
344    {
345        return $this->shortFormat;
346    }
347}
348