AmigaSSH: SSH2 client and server for the Amiga
Intro
There was no working SSH2 client/server for the Amiga supporting modern encryption, but I wanted both.
Should I start porting OpenSSH? Or should I use AmiSSL?
-> Considering the sizes of both - both are monsters, the AmiSSL library is larger than 3MB - and then the missing support for the 68000... So I decided to start from scratch.
Since I already had Java and C++ code for some of the cryptographic methods, C++ was my choice for the Amiga. I just needed some info for the "how does it work" and was ready to start.
The resources:
Coding AmigaSSH
I started with a simple TCP client to connect to a server and started to implement the packets. Since it should work with the standard SSH2 server on my Linux servers, I used these for testing. The most importand tool was the dump function to print received and sent packages.
First packets
And here starts the fun...
-> client hello packet
The protocol states to start with a simple helle message, containing the protocol version and some vendor specific text, so I sent
0000
53 53 48 2D 32 2E 30
2D
6D 69 6E 69 53 53 48 2D
SSH-2.0
-
miniSSH-
0010
30 2E 31
0D 0A
0.1
A plaintext with a CRLF at the end.
- starts with the SSH version
- then some identifier for the vendor
<- server hello packet
And to no surprise I could also read the server's hello packet:
0000 53 53 48 2D 32 2E 30 2D 4F 70 65 6E 53 53 48 5F SSH-2.0-OpenSSH_
0010 39 2E 36 9.6
Also a plaintext with the same structure and a CRLF at the end.
-> client key exchange init
Since I wanted to support only one variant I managed to assemble the packet to announce my supported variants:
0000
00 00 00 B4
0B
14
1D FC 61 19 C0 C7 C6 75 9C 13
........a....u..
0010
E2 E1 98 5F 3E 13
00 00 00 11 63 75 72 76 65 32
..._>.....curve2
0020
35 35 31 39 2D 73 68 61 32 35 36
00 00 00 0B 73
5519-sha256....s
0030
73 68 2D 65 64 32 35 35 31 39
00 00 00 16 61 65
sh-ed25519....ae
0040
73 31 32 38 2D 67 63 6D 40 6F 70 65 6E 73 73 68
s128-gcm@openssh
0050
2E 63 6F 6D
00 00 00 16 61 65 73 31 32 38 2D 67
.com....aes128-g
0060
63 6D 40 6F 70 65 6E 73 73 68 2E 63 6F 6D
00 00
cm@openssh.com..
0070
00 0D 68 6D 61 63 2D 73 68 61 32 2D 32 35 36
00
..hmac-sha2-256.
0080
00 00 0D 68 6D 61 63 2D 73 68 61 32 2D 32 35 36
...hmac-sha2-256
0090
00 00 00 04 6E 6F 6E 65
00 00 00 04 6E 6F 6E 65
....none....none
00a0
00 00 00 00 00 00 00 00 00 00 00 00 00
FF C9 71
................
00b0
ED 64 38 B8 6C 92 27 BA
........
- The first 4 bytes are the packet length, here 0xB4
- Followed by 1 byte containing the pad length, here 0xB
- The next byte defines the packet type 0x14
- a nonce of 16 random bytes
- the ssh-string "curve25519-sha256" with length 0x11, which is our supported kex_algorithm.
- the ssh-string "ssh-ed25519" with length 0xB, which is the supported server_host_key_algorithm.
- the ssh-string "aes128-gcm@openssh.com", length 0x16, which is the supported encryption_algorithms_client_to_server.
- the ssh-string "aes128-gcm@openssh.com", length 0x16, which is the supported encryption_algorithms_server_to_client.
- the ssh-string "hmac-sha2-256", length 0xD, which is the supported mac_algorithms_client_to_server.
- the ssh-string "hmac-sha2-256", length 0xD, which is the supported mac_algorithms_server_to_client.
- the ssh-string "none", length 0xD, which is the supported compression_algorithms_client_to_server.
- the ssh-string "none", length 0xD, which is the supported compression_algorithms_server_to_client.
- languages_client_to_server, languages_server_to_client, first_kex_packet_follows, uint32 0 (reserved for future extension)
- and the random padding bytes
We generate the random nonce, the rest is constant. There is no need to track the nonce, it contributes to the hash, that's it.
<- server key exchange init
The server sent a packet with the same structure, but way more options, a ridiculous large amount of options...
I could have checked if the server matches, but since my client offers only one option there's nothing to check. Nice!
-> client ecdh init
The client sends it's public key.
0000
00 00 00 2C
06
1E
00 00 00 20 92 D7 57 3C 2C A3
...,..... ..W<,.
0010
C0 70 97 5E 5B 81 0A AF 27 D4 9B B0 CF 50 C7 1B
.p.^[...'....P..
0020
A2 F2 82 6E C2 FB 33 EB B1 78
00 00 00 00 00 00 ...n..3..x......
- The first 4 bytes are the packet length, here 0x2C
- Followed by 1 byte containing the pad length, here 0x06
- The next byte defines the packet type 0x1E
- The client's public key
<- server ecdh reply
The server packet contains the servers's public key.
0000
00 00 00 BC
08
1F
00 00 00 33 00 00 00 0B 73 73
.........3....ss
0010
68 2D 65 64 32 35 35 31 39
00 00 00 20 AC AF 4D
h-ed25519... ..M
0020
62 55 87 43 BC 72 E4 2F C9 7C B0 3A 35 1F A5 62
bU.C.r./.|.:5..b
0030
FB F5 DC E8 17 81 1F 00 8D 28 6F 49 5E
00 00 00
.........(oI^...
0040
20 5C 7E 63 16 BE 68 62 34 5A 28 55 69 B3 92 B2
\~c..hb4Z(Ui...
0050
00 4F 6C 98 16 29 70 FD 6B 59 74 13 EE 2B D3 52
.Ol..)p.kYt..+.R
0060
71
00 00 00 53 00 00 00 0B 73 73 68 2D 65 64 32
q...S....ssh-ed2
0070
35 35 31 39
00 00 00 40 4B 21 25 1A D8 60 71 2B
5519...@K!%..`q+
0080
B0 E9 96 61 B8 42 29 29 B4 45 6C 05 60 DA 4A 20
...a.B)).El.`.J
0090
E9 16 65 6F 09 60 A2 59 25 9C 44 3F 0C B6 89 ED
..eo.`.Y%.D?....
00A0
16 B8 59 97 F5 3D 6B 21 E2 0C B9 90 5F 39 99 2F
..Y..=k!...._9./
00B0
4A 56 46 3A B0 BD 71 0E
00 00 00 00 00 00 00 00 JVF:..q.........
- The first 4 bytes are the packet length, here 0xBC
- Followed by 1 byte containing the pad length, here 0x08
- The next byte defines the packet type 0x1F
- the host public key type
- containing the host public key
The server's public key
- the signature method
- containing the host signature
And here starts the fun to learn more about ED25519 and X22519:
- Both are using the same curve in different representations.
- But the ED25519 an additional modulo is applied... let's make it short: you need both.
I first managed to do the X22519 multiplication correctly. Since
- host public key = X25519base * host secret key
And
- client public key = X25519base * client secret key
Both sides can compute the same value by multiplying the received public key with its own private key:
- shared secret = X25519base * client secret key * server secret key
- shared secret = X25519base * server secret key * client secret
Checking the signature was more work since ED25519 is not X25519!!.
Luckily I had my Java code plus there are python reference implementations around to create test data and debug. For C implementations you my consider supercop or ed25519-donna where donna seems to be the fastest implementation. But donna needs 64 bit integers and 32 bit multiplication... So I started with a supercop variant using 16 bit integers with 8 bits used and dropped all superfluous code which should help to avoid timing attacks. Well, on the Amiga anyone can read anything -> drop it! I also removed some optimizations which resulted in big big code.
Eventually it worked out.
After putting the correct data into a SHA512 hash I also managed to create the correct keymaterial.
-> client newkeys
<- server newkeys
Both are empty packets to activate the encryption.
From now on the messages are encrypted.
-> client's service request
<- servers's service response
Now the client wants to perform some authentication and the server replies the supported methods.
0000
05
00 00 00 0C 73 73 68 2D 75 73 65 72 61 75 74
.....ssh-useraut
0010
68
- message type 5 = service request
- the requested service: ssh-userauth
-> client's user auth request
The first attempt is without any authentication
0000
32
00 00 00 06 73 74 65 66 61 6E
00 00 00 0E 73
2....stefan....s
0010
73 68 2D 63 6F 6E 6E 65 63 74 69 6F 6E
00 00 00
sh-connection...
0020
04 6E 6F 6E 65
.none
- message type 0x32 = userauth request
- username = stefan
- purpose = ssh-connection
- auth method = none
<- server's user auth response
The server replies with an authentication failure and the list of supported methods.
0000
33
00 00 00 08 70 61 73 73 77 6F 72 64 00
3....password.
- message type 0x33 = userauth failure
- supported methods = password
O.K. I stop here with the dumps, or you could read my secret password^^
Terminal fun
After establishing a SSH connection I had to work on key translation. On the server side there are many terminal emulations available, even some for the Amiga, but none seems to work properly. So I started to code terminal emulation too. Together with the key translations I finally made a login with a good look and feel:
After all also the midnight commander worked properly:
(Note: the CLI title changed too^^ plus you can use the mouse to click)