Subversion Repositories oidplus

Rev

Rev 874 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
827 daniel-mar 1
<?php
2
 
3
/**
4
 * OpenSSH Formatted EC Key Handler
5
 *
6
 * PHP version 5
7
 *
8
 * Place in $HOME/.ssh/authorized_keys
9
 *
10
 * @author    Jim Wigginton <terrafrost@php.net>
11
 * @copyright 2015 Jim Wigginton
12
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
13
 * @link      http://phpseclib.sourceforge.net
14
 */
15
 
16
namespace phpseclib3\Crypt\EC\Formats\Keys;
17
 
18
use phpseclib3\Common\Functions\Strings;
19
use phpseclib3\Crypt\Common\Formats\Keys\OpenSSH as Progenitor;
20
use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve;
21
use phpseclib3\Crypt\EC\Curves\Ed25519;
22
use phpseclib3\Exception\UnsupportedCurveException;
23
use phpseclib3\Math\BigInteger;
24
 
25
/**
26
 * OpenSSH Formatted EC Key Handler
27
 *
28
 * @author  Jim Wigginton <terrafrost@php.net>
29
 */
30
abstract class OpenSSH extends Progenitor
31
{
32
    use Common;
33
 
34
    /**
35
     * Supported Key Types
36
     *
37
     * @var array
38
     */
39
    protected static $types = [
40
        'ecdsa-sha2-nistp256',
41
        'ecdsa-sha2-nistp384',
42
        'ecdsa-sha2-nistp521',
43
        'ssh-ed25519'
44
    ];
45
 
46
    /**
47
     * Break a public or private key down into its constituent components
48
     *
49
     * @param string $key
50
     * @param string $password optional
51
     * @return array
52
     */
53
    public static function load($key, $password = '')
54
    {
55
        $parsed = parent::load($key, $password);
56
 
57
        if (isset($parsed['paddedKey'])) {
58
            $paddedKey = $parsed['paddedKey'];
59
            list($type) = Strings::unpackSSH2('s', $paddedKey);
60
            if ($type != $parsed['type']) {
61
                throw new \RuntimeException("The public and private keys are not of the same type ($type vs $parsed[type])");
62
            }
63
            if ($type == 'ssh-ed25519') {
64
                list(, $key, $comment) = Strings::unpackSSH2('sss', $paddedKey);
65
                $key = libsodium::load($key);
66
                $key['comment'] = $comment;
67
                return $key;
68
            }
69
            list($curveName, $publicKey, $privateKey, $comment) = Strings::unpackSSH2('ssis', $paddedKey);
70
            $curve = self::loadCurveByParam(['namedCurve' => $curveName]);
71
            $curve->rangeCheck($privateKey);
72
            return [
73
                'curve' => $curve,
74
                'dA' => $privateKey,
75
                'QA' => self::extractPoint("\0$publicKey", $curve),
76
                'comment' => $comment
77
            ];
78
        }
79
 
80
        if ($parsed['type'] == 'ssh-ed25519') {
81
            if (Strings::shift($parsed['publicKey'], 4) != "\0\0\0\x20") {
82
                throw new \RuntimeException('Length of ssh-ed25519 key should be 32');
83
            }
84
 
85
            $curve = new Ed25519();
86
            $qa = self::extractPoint($parsed['publicKey'], $curve);
87
        } else {
88
            list($curveName, $publicKey) = Strings::unpackSSH2('ss', $parsed['publicKey']);
89
            $curveName = '\phpseclib3\Crypt\EC\Curves\\' . $curveName;
90
            $curve = new $curveName();
91
 
92
            $qa = self::extractPoint("\0" . $publicKey, $curve);
93
        }
94
 
95
        return [
96
            'curve' => $curve,
97
            'QA' => $qa,
98
            'comment' => $parsed['comment']
99
        ];
100
    }
101
 
102
    /**
103
     * Returns the alias that corresponds to a curve
104
     *
105
     * @return string
106
     */
107
    private static function getAlias(BaseCurve $curve)
108
    {
109
        self::initialize_static_variables();
110
 
111
        $reflect = new \ReflectionClass($curve);
112
        $name = $reflect->getShortName();
113
 
114
        $oid = self::$curveOIDs[$name];
115
        $aliases = array_filter(self::$curveOIDs, function ($v) use ($oid) {
116
            return $v == $oid;
117
        });
118
        $aliases = array_keys($aliases);
119
 
120
        for ($i = 0; $i < count($aliases); $i++) {
121
            if (in_array('ecdsa-sha2-' . $aliases[$i], self::$types)) {
122
                $alias = $aliases[$i];
123
                break;
124
            }
125
        }
126
 
127
        if (!isset($alias)) {
128
            throw new UnsupportedCurveException($name . ' is not a curve that the OpenSSH plugin supports');
129
        }
130
 
131
        return $alias;
132
    }
133
 
134
    /**
135
     * Convert an EC public key to the appropriate format
136
     *
137
     * @param \phpseclib3\Crypt\EC\BaseCurves\Base $curve
138
     * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
139
     * @param array $options optional
140
     * @return string
141
     */
142
    public static function savePublicKey(BaseCurve $curve, array $publicKey, array $options = [])
143
    {
144
        $comment = isset($options['comment']) ? $options['comment'] : self::$comment;
145
 
146
        if ($curve instanceof Ed25519) {
147
            $key = Strings::packSSH2('ss', 'ssh-ed25519', $curve->encodePoint($publicKey));
148
 
149
            if (isset($options['binary']) ? $options['binary'] : self::$binary) {
150
                return $key;
151
            }
152
 
153
            $key = 'ssh-ed25519 ' . base64_encode($key) . ' ' . $comment;
154
            return $key;
155
        }
156
 
157
        $alias = self::getAlias($curve);
158
 
159
        $points = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes();
160
        $key = Strings::packSSH2('sss', 'ecdsa-sha2-' . $alias, $alias, $points);
161
 
162
        if (isset($options['binary']) ? $options['binary'] : self::$binary) {
163
            return $key;
164
        }
165
 
166
        $key = 'ecdsa-sha2-' . $alias . ' ' . base64_encode($key) . ' ' . $comment;
167
 
168
        return $key;
169
    }
170
 
171
    /**
172
     * Convert a private key to the appropriate format.
173
     *
174
     * @param \phpseclib3\Math\BigInteger $privateKey
175
     * @param \phpseclib3\Crypt\EC\Curves\Ed25519 $curve
176
     * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
1042 daniel-mar 177
     * @param string $secret optional
827 daniel-mar 178
     * @param string $password optional
179
     * @param array $options optional
180
     * @return string
181
     */
1042 daniel-mar 182
    public static function savePrivateKey(BigInteger $privateKey, BaseCurve $curve, array $publicKey, $secret = null, $password = '', array $options = [])
827 daniel-mar 183
    {
184
        if ($curve instanceof Ed25519) {
1042 daniel-mar 185
            if (!isset($secret)) {
827 daniel-mar 186
                throw new \RuntimeException('Private Key does not have a secret set');
187
            }
1042 daniel-mar 188
            if (strlen($secret) != 32) {
827 daniel-mar 189
                throw new \RuntimeException('Private Key secret is not of the correct length');
190
            }
191
 
192
            $pubKey = $curve->encodePoint($publicKey);
193
 
194
            $publicKey = Strings::packSSH2('ss', 'ssh-ed25519', $pubKey);
1042 daniel-mar 195
            $privateKey = Strings::packSSH2('sss', 'ssh-ed25519', $pubKey, $secret . $pubKey);
827 daniel-mar 196
 
197
            return self::wrapPrivateKey($publicKey, $privateKey, $password, $options);
198
        }
199
 
200
        $alias = self::getAlias($curve);
201
 
202
        $points = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes();
203
        $publicKey = self::savePublicKey($curve, $publicKey, ['binary' => true]);
204
 
205
        $privateKey = Strings::packSSH2('sssi', 'ecdsa-sha2-' . $alias, $alias, $points, $privateKey);
206
 
207
        return self::wrapPrivateKey($publicKey, $privateKey, $password, $options);
208
    }
209
}