Subversion Repositories oidplus

Rev

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