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