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
 * Pure-PHP ssh-agent client.
5
 *
6
 * {@internal See http://api.libssh.org/rfc/PROTOCOL.agent}
7
 *
8
 * PHP version 5
9
 *
874 daniel-mar 10
 * @category  System
11
 * @package   SSH\Agent
827 daniel-mar 12
 * @author    Jim Wigginton <terrafrost@php.net>
13
 * @copyright 2009 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\System\SSH\Agent;
19
 
20
use phpseclib3\Common\Functions\Strings;
21
use phpseclib3\Crypt\Common\PrivateKey;
22
use phpseclib3\Crypt\Common\PublicKey;
23
use phpseclib3\Crypt\DSA;
24
use phpseclib3\Crypt\EC;
25
use phpseclib3\Crypt\RSA;
26
use phpseclib3\Exception\UnsupportedAlgorithmException;
27
use phpseclib3\System\SSH\Agent;
28
 
29
/**
30
 * Pure-PHP ssh-agent client identity object
31
 *
32
 * Instantiation should only be performed by \phpseclib3\System\SSH\Agent class.
33
 * This could be thought of as implementing an interface that phpseclib3\Crypt\RSA
34
 * implements. ie. maybe a Net_SSH_Auth_PublicKey interface or something.
35
 * The methods in this interface would be getPublicKey and sign since those are the
36
 * methods phpseclib looks for to perform public key authentication.
37
 *
874 daniel-mar 38
 * @package SSH\Agent
827 daniel-mar 39
 * @author  Jim Wigginton <terrafrost@php.net>
874 daniel-mar 40
 * @access  internal
827 daniel-mar 41
 */
42
class Identity implements PrivateKey
43
{
874 daniel-mar 44
    use \phpseclib3\System\SSH\Common\Traits\ReadBytes;
827 daniel-mar 45
 
46
    // Signature Flags
47
    // See https://tools.ietf.org/html/draft-miller-ssh-agent-00#section-5.3
48
    const SSH_AGENT_RSA2_256 = 2;
49
    const SSH_AGENT_RSA2_512 = 4;
50
 
51
    /**
52
     * Key Object
53
     *
54
     * @var PublicKey
874 daniel-mar 55
     * @access private
827 daniel-mar 56
     * @see self::getPublicKey()
57
     */
58
    private $key;
59
 
60
    /**
61
     * Key Blob
62
     *
63
     * @var string
874 daniel-mar 64
     * @access private
827 daniel-mar 65
     * @see self::sign()
66
     */
67
    private $key_blob;
68
 
69
    /**
70
     * Socket Resource
71
     *
72
     * @var resource
874 daniel-mar 73
     * @access private
827 daniel-mar 74
     * @see self::sign()
75
     */
76
    private $fsock;
77
 
78
    /**
79
     * Signature flags
80
     *
81
     * @var int
874 daniel-mar 82
     * @access private
827 daniel-mar 83
     * @see self::sign()
84
     * @see self::setHash()
85
     */
86
    private $flags = 0;
87
 
88
    /**
89
     * Curve Aliases
90
     *
91
     * @var array
874 daniel-mar 92
     * @access private
827 daniel-mar 93
     */
94
    private static $curveAliases = [
95
        'secp256r1' => 'nistp256',
96
        'secp384r1' => 'nistp384',
97
        'secp521r1' => 'nistp521',
98
        'Ed25519' => 'Ed25519'
99
    ];
100
 
101
    /**
102
     * Default Constructor.
103
     *
104
     * @param resource $fsock
874 daniel-mar 105
     * @access private
827 daniel-mar 106
     */
107
    public function __construct($fsock)
108
    {
109
        $this->fsock = $fsock;
110
    }
111
 
112
    /**
113
     * Set Public Key
114
     *
115
     * Called by \phpseclib3\System\SSH\Agent::requestIdentities()
116
     *
117
     * @param \phpseclib3\Crypt\Common\PublicKey $key
874 daniel-mar 118
     * @access private
827 daniel-mar 119
     */
120
    public function withPublicKey($key)
121
    {
122
        if ($key instanceof EC) {
123
            if (is_array($key->getCurve()) || !isset(self::$curveAliases[$key->getCurve()])) {
124
                throw new UnsupportedAlgorithmException('The only supported curves are nistp256, nistp384, nistp512 and Ed25519');
125
            }
126
        }
127
 
128
        $new = clone $this;
129
        $new->key = $key;
130
        return $new;
131
    }
132
 
133
    /**
134
     * Set Public Key
135
     *
136
     * Called by \phpseclib3\System\SSH\Agent::requestIdentities(). The key blob could be extracted from $this->key
137
     * but this saves a small amount of computation.
138
     *
139
     * @param string $key_blob
874 daniel-mar 140
     * @access private
827 daniel-mar 141
     */
142
    public function withPublicKeyBlob($key_blob)
143
    {
144
        $new = clone $this;
145
        $new->key_blob = $key_blob;
146
        return $new;
147
    }
148
 
149
    /**
150
     * Get Public Key
151
     *
152
     * Wrapper for $this->key->getPublicKey()
153
     *
154
     * @param string $type optional
155
     * @return mixed
874 daniel-mar 156
     * @access public
827 daniel-mar 157
     */
158
    public function getPublicKey($type = 'PKCS8')
159
    {
160
        return $this->key;
161
    }
162
 
163
    /**
164
     * Sets the hash
165
     *
166
     * @param string $hash
874 daniel-mar 167
     * @access public
827 daniel-mar 168
     */
169
    public function withHash($hash)
170
    {
171
        $new = clone $this;
172
 
173
        $hash = strtolower($hash);
174
 
175
        if ($this->key instanceof RSA) {
176
            $new->flags = 0;
177
            switch ($hash) {
178
                case 'sha1':
179
                    break;
180
                case 'sha256':
181
                    $new->flags = self::SSH_AGENT_RSA2_256;
182
                    break;
183
                case 'sha512':
184
                    $new->flags = self::SSH_AGENT_RSA2_512;
185
                    break;
186
                default:
187
                    throw new UnsupportedAlgorithmException('The only supported hashes for RSA are sha1, sha256 and sha512');
188
            }
189
        }
190
        if ($this->key instanceof EC) {
191
            switch ($this->key->getCurve()) {
192
                case 'secp256r1':
193
                    $expectedHash = 'sha256';
194
                    break;
195
                case 'secp384r1':
196
                    $expectedHash = 'sha384';
197
                    break;
198
                //case 'secp521r1':
199
                //case 'Ed25519':
200
                default:
201
                    $expectedHash = 'sha512';
202
            }
203
            if ($hash != $expectedHash) {
204
                throw new UnsupportedAlgorithmException('The only supported hash for ' . self::$curveAliases[$this->key->getCurve()] . ' is ' . $expectedHash);
205
            }
206
        }
207
        if ($this->key instanceof DSA) {
208
            if ($hash != 'sha1') {
209
                throw new UnsupportedAlgorithmException('The only supported hash for DSA is sha1');
210
            }
211
        }
212
        return $new;
213
    }
214
 
215
    /**
216
     * Sets the padding
217
     *
218
     * Only PKCS1 padding is supported
219
     *
220
     * @param string $padding
874 daniel-mar 221
     * @access public
827 daniel-mar 222
     */
223
    public function withPadding($padding)
224
    {
225
        if (!$this->key instanceof RSA) {
226
            throw new UnsupportedAlgorithmException('Only RSA keys support padding');
227
        }
228
        if ($padding != RSA::SIGNATURE_PKCS1 && $padding != RSA::SIGNATURE_RELAXED_PKCS1) {
229
            throw new UnsupportedAlgorithmException('ssh-agent can only create PKCS1 signatures');
230
        }
231
        return $this;
232
    }
233
 
234
    /**
235
     * Determines the signature padding mode
236
     *
237
     * Valid values are: ASN1, SSH2, Raw
238
     *
874 daniel-mar 239
     * @access public
827 daniel-mar 240
     * @param string $format
241
     */
242
    public function withSignatureFormat($format)
243
    {
244
        if ($this->key instanceof RSA) {
245
            throw new UnsupportedAlgorithmException('Only DSA and EC keys support signature format setting');
246
        }
247
        if ($format != 'SSH2') {
248
            throw new UnsupportedAlgorithmException('Only SSH2-formatted signatures are currently supported');
249
        }
250
 
251
        return $this;
252
    }
253
 
254
    /**
255
     * Returns the curve
256
     *
257
     * Returns a string if it's a named curve, an array if not
258
     *
874 daniel-mar 259
     * @access public
827 daniel-mar 260
     * @return string|array
261
     */
262
    public function getCurve()
263
    {
264
        if (!$this->key instanceof EC) {
265
            throw new UnsupportedAlgorithmException('Only EC keys have curves');
266
        }
267
 
268
        return $this->key->getCurve();
269
    }
270
 
271
    /**
272
     * Create a signature
273
     *
274
     * See "2.6.2 Protocol 2 private key signature request"
275
     *
276
     * @param string $message
277
     * @return string
278
     * @throws \RuntimeException on connection errors
279
     * @throws \phpseclib3\Exception\UnsupportedAlgorithmException if the algorithm is unsupported
874 daniel-mar 280
     * @access public
827 daniel-mar 281
     */
282
    public function sign($message)
283
    {
284
        // the last parameter (currently 0) is for flags and ssh-agent only defines one flag (for ssh-dss): SSH_AGENT_OLD_SIGNATURE
285
        $packet = Strings::packSSH2(
286
            'CssN',
287
            Agent::SSH_AGENTC_SIGN_REQUEST,
288
            $this->key_blob,
289
            $message,
290
            $this->flags
291
        );
292
        $packet = Strings::packSSH2('s', $packet);
293
        if (strlen($packet) != fputs($this->fsock, $packet)) {
294
            throw new \RuntimeException('Connection closed during signing');
295
        }
296
 
297
        $length = current(unpack('N', $this->readBytes(4)));
298
        $packet = $this->readBytes($length);
299
 
300
        list($type, $signature_blob) = Strings::unpackSSH2('Cs', $packet);
301
        if ($type != Agent::SSH_AGENT_SIGN_RESPONSE) {
302
            throw new \RuntimeException('Unable to retrieve signature');
303
        }
304
 
305
        if (!$this->key instanceof RSA) {
306
            return $signature_blob;
307
        }
308
 
309
        list($type, $signature_blob) = Strings::unpackSSH2('ss', $signature_blob);
310
 
311
        return $signature_blob;
312
    }
313
 
314
    /**
315
     * Returns the private key
316
     *
317
     * @param string $type
318
     * @param array $options optional
319
     * @return string
320
     */
321
    public function toString($type, array $options = [])
322
    {
323
        throw new \RuntimeException('ssh-agent does not provide a mechanism to get the private key');
324
    }
325
 
326
    /**
327
     * Sets the password
328
     *
874 daniel-mar 329
     * @access public
827 daniel-mar 330
     * @param string|bool $password
331
     * @return never
332
     */
333
    public function withPassword($password = false)
334
    {
335
        throw new \RuntimeException('ssh-agent does not provide a mechanism to get the private key');
336
    }
337
}