1<?php
2
3/**
4 * PKCS#8 Formatted DSA Key Handler
5 *
6 * PHP version 5
7 *
8 * Processes keys with the following headers:
9 *
10 * -----BEGIN ENCRYPTED PRIVATE KEY-----
11 * -----BEGIN PRIVATE KEY-----
12 * -----BEGIN PUBLIC KEY-----
13 *
14 * Analogous to ssh-keygen's pkcs8 format (as specified by -m). Although PKCS8
15 * is specific to private keys it's basically creating a DER-encoded wrapper
16 * for keys. This just extends that same concept to public keys (much like ssh-keygen)
17 *
18 * @category  Crypt
19 * @package   DSA
20 * @author    Jim Wigginton <terrafrost@php.net>
21 * @copyright 2015 Jim Wigginton
22 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
23 * @link      http://phpseclib.sourceforge.net
24 */
25
26namespace phpseclib3\Crypt\DSA\Formats\Keys;
27
28use phpseclib3\Common\Functions\Strings;
29use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor;
30use phpseclib3\File\ASN1;
31use phpseclib3\File\ASN1\Maps;
32use phpseclib3\Math\BigInteger;
33
34/**
35 * PKCS#8 Formatted DSA Key Handler
36 *
37 * @package DSA
38 * @author  Jim Wigginton <terrafrost@php.net>
39 * @access  public
40 */
41abstract class PKCS8 extends Progenitor
42{
43    /**
44     * OID Name
45     *
46     * @var string
47     * @access private
48     */
49    const OID_NAME = 'id-dsa';
50
51    /**
52     * OID Value
53     *
54     * @var string
55     * @access private
56     */
57    const OID_VALUE = '1.2.840.10040.4.1';
58
59    /**
60     * Child OIDs loaded
61     *
62     * @var bool
63     * @access private
64     */
65    protected static $childOIDsLoaded = false;
66
67    /**
68     * Break a public or private key down into its constituent components
69     *
70     * @access public
71     * @param string $key
72     * @param string $password optional
73     * @return array
74     */
75    public static function load($key, $password = '')
76    {
77        if (!Strings::is_stringable($key)) {
78            throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
79        }
80
81        $isPublic = strpos($key, 'PUBLIC') !== false;
82
83        $key = parent::load($key, $password);
84
85        $type = isset($key['privateKey']) ? 'privateKey' : 'publicKey';
86
87        switch (true) {
88            case !$isPublic && $type == 'publicKey':
89                throw new \UnexpectedValueException('Human readable string claims non-public key but DER encoded string claims public key');
90            case $isPublic && $type == 'privateKey':
91                throw new \UnexpectedValueException('Human readable string claims public key but DER encoded string claims private key');
92        }
93
94        $decoded = ASN1::decodeBER($key[$type . 'Algorithm']['parameters']->element);
95        if (empty($decoded)) {
96            throw new \RuntimeException('Unable to decode BER of parameters');
97        }
98        $components = ASN1::asn1map($decoded[0], Maps\DSAParams::MAP);
99        if (!is_array($components)) {
100            throw new \RuntimeException('Unable to perform ASN1 mapping on parameters');
101        }
102
103        $decoded = ASN1::decodeBER($key[$type]);
104        if (empty($decoded)) {
105            throw new \RuntimeException('Unable to decode BER');
106        }
107
108        $var = $type == 'privateKey' ? 'x' : 'y';
109        $components[$var] = ASN1::asn1map($decoded[0], Maps\DSAPublicKey::MAP);
110        if (!$components[$var] instanceof BigInteger) {
111            throw new \RuntimeException('Unable to perform ASN1 mapping');
112        }
113
114        if (isset($key['meta'])) {
115            $components['meta'] = $key['meta'];
116        }
117
118        return $components;
119    }
120
121    /**
122     * Convert a private key to the appropriate format.
123     *
124     * @access public
125     * @param \phpseclib3\Math\BigInteger $p
126     * @param \phpseclib3\Math\BigInteger $q
127     * @param \phpseclib3\Math\BigInteger $g
128     * @param \phpseclib3\Math\BigInteger $y
129     * @param \phpseclib3\Math\BigInteger $x
130     * @param string $password optional
131     * @param array $options optional
132     * @return string
133     */
134    public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, $password = '', array $options = [])
135    {
136        $params = [
137            'p' => $p,
138            'q' => $q,
139            'g' => $g
140        ];
141        $params = ASN1::encodeDER($params, Maps\DSAParams::MAP);
142        $params = new ASN1\Element($params);
143        $key = ASN1::encodeDER($x, Maps\DSAPublicKey::MAP);
144        return self::wrapPrivateKey($key, [], $params, $password, null, '', $options);
145    }
146
147    /**
148     * Convert a public key to the appropriate format
149     *
150     * @access public
151     * @param \phpseclib3\Math\BigInteger $p
152     * @param \phpseclib3\Math\BigInteger $q
153     * @param \phpseclib3\Math\BigInteger $g
154     * @param \phpseclib3\Math\BigInteger $y
155     * @param array $options optional
156     * @return string
157     */
158    public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, array $options = [])
159    {
160        $params = [
161            'p' => $p,
162            'q' => $q,
163            'g' => $g
164        ];
165        $params = ASN1::encodeDER($params, Maps\DSAParams::MAP);
166        $params = new ASN1\Element($params);
167        $key = ASN1::encodeDER($y, Maps\DSAPublicKey::MAP);
168        return self::wrapPublicKey($key, $params);
169    }
170}
171