Skip to content

Commit 4fcc222

Browse files
authored
Merge pull request #6 from DivineOmega/feature/fingerprint
SSH server fingerprinting
2 parents 261d2ba + 6c6a010 commit 4fcc222

File tree

3 files changed

+130
-0
lines changed

3 files changed

+130
-0
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ composer require divineomega/php-ssh-connection
1515

1616
## Usage
1717

18+
See the following basic usage instructions.
19+
1820
```php
1921
$connection = (new SSHConnection())
2022
->to('test.rebex.net')
@@ -32,3 +34,21 @@ $command->getError(); // ''
3234
$connection->upload($localPath, $remotePath);
3335
$connection->download($remotePath, $localPath);
3436
```
37+
38+
For security, you can fingerprint the remote server and verify the fingerprint remain the same
39+
upon each subsequent connection.
40+
41+
```php
42+
$fingerprint = $connection->fingerprint();
43+
44+
if ($newConnection->fingerprint() != $fingerprint) {
45+
throw new Exception('Fingerprint does not match!');
46+
}
47+
```
48+
49+
If you wish, you can specify the type of fingerprint you wish to retrieve.
50+
51+
```php
52+
$md5Fingerprint = $connection->fingerprint(SSHConnection::FINGERPRINT_MD5); // default
53+
$sha1Fingerprint = $connection->fingerprint(SSHConnection::FINGERPRINT_SHA1);
54+
```

src/SSHConnection.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010

1111
class SSHConnection
1212
{
13+
const FINGERPRINT_MD5 = 'md5';
14+
const FINGERPRINT_SHA1 = 'sha1';
15+
1316
private $hostname;
1417
private $port = 22;
1518
private $username;
@@ -112,6 +115,25 @@ public function run(string $command): SSHCommand
112115
return new SSHCommand($this->ssh, $command);
113116
}
114117

118+
public function fingerprint(string $type = self::FINGERPRINT_MD5)
119+
{
120+
if (!$this->connected) {
121+
throw new RuntimeException('Unable to get fingerprint when not connected.');
122+
}
123+
124+
$hostKey = substr($this->ssh->getServerPublicHostKey(), 8);
125+
126+
switch ($type) {
127+
case 'md5':
128+
return strtoupper(md5($hostKey));
129+
130+
case 'sha1':
131+
return strtoupper(sha1($hostKey));
132+
}
133+
134+
throw new InvalidArgumentException('Invalid fingerprint type specified.');
135+
}
136+
115137
public function upload(string $localPath, string $remotePath): bool
116138
{
117139
if (!$this->connected) {

tests/Integration/SSHConnectionTest.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,92 @@ public function testSSHConnectionWithPassword()
7272
$this->assertEquals('', $command->getError());
7373
$this->assertEquals('', $command->getRawError());
7474
}
75+
76+
public function testMd5Fingerprint()
77+
{
78+
$connection1 = (new SSHConnection())
79+
->to('localhost')
80+
->onPort(22)
81+
->as('travis')
82+
->withPrivateKey('/home/travis/.ssh/id_rsa')
83+
->connect();
84+
85+
$connection2 = (new SSHConnection())
86+
->to('localhost')
87+
->onPort(22)
88+
->as('travis')
89+
->withPrivateKey('/home/travis/.ssh/id_rsa')
90+
->connect();
91+
92+
$this->assertEquals(
93+
$connection1->fingerprint(SSHConnection::FINGERPRINT_MD5),
94+
$connection2->fingerprint(SSHConnection::FINGERPRINT_MD5)
95+
);
96+
}
97+
98+
public function testSha1Fingerprint()
99+
{
100+
$connection1 = (new SSHConnection())
101+
->to('localhost')
102+
->onPort(22)
103+
->as('travis')
104+
->withPrivateKey('/home/travis/.ssh/id_rsa')
105+
->connect();
106+
107+
$connection2 = (new SSHConnection())
108+
->to('localhost')
109+
->onPort(22)
110+
->as('travis')
111+
->withPrivateKey('/home/travis/.ssh/id_rsa')
112+
->connect();
113+
114+
$this->assertEquals(
115+
$connection1->fingerprint(SSHConnection::FINGERPRINT_SHA1),
116+
$connection2->fingerprint(SSHConnection::FINGERPRINT_SHA1)
117+
);
118+
}
119+
120+
public function testMd5FingerprintFailure()
121+
{
122+
$connection1 = (new SSHConnection())
123+
->to('localhost')
124+
->onPort(22)
125+
->as('travis')
126+
->withPrivateKey('/home/travis/.ssh/id_rsa')
127+
->connect();
128+
129+
$connection2 = (new SSHConnection())
130+
->to('test.rebex.net')
131+
->onPort(22)
132+
->as('demo')
133+
->withPassword('password')
134+
->connect();
135+
136+
$this->assertNotEquals(
137+
$connection1->fingerprint(SSHConnection::FINGERPRINT_MD5),
138+
$connection2->fingerprint(SSHConnection::FINGERPRINT_MD5)
139+
);
140+
}
141+
142+
public function testSha1FingerprintFailure()
143+
{
144+
$connection1 = (new SSHConnection())
145+
->to('localhost')
146+
->onPort(22)
147+
->as('travis')
148+
->withPrivateKey('/home/travis/.ssh/id_rsa')
149+
->connect();
150+
151+
$connection2 = (new SSHConnection())
152+
->to('test.rebex.net')
153+
->onPort(22)
154+
->as('demo')
155+
->withPassword('password')
156+
->connect();
157+
158+
$this->assertNotEquals(
159+
$connection1->fingerprint(SSHConnection::FINGERPRINT_SHA1),
160+
$connection2->fingerprint(SSHConnection::FINGERPRINT_SHA1)
161+
);
162+
}
75163
}

0 commit comments

Comments
 (0)