Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ssh can't connect to modern openssh servers #72

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions coro/ssh/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
import transport
import util
import wrapper
import crypto
39 changes: 39 additions & 0 deletions coro/ssh/cipher/aes256_ctr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

#
# ssh.cipher.aes256_ctr
#
# Implements the AES256 cipher in CTR mode.
#

from coro.ssh.cipher import SSH_Cipher_Method
from Crypto.Cipher import AES
from Crypto.Util import Counter
import os

from coro.log import Facility
LOG = Facility ('aes256_ctr')

class AES256_CTR (SSH_Cipher_Method):

name = 'aes256-ctr'
block_size = AES.block_size
key_size = 32
iv_size = 16
cipher = None

def encrypt (self, data):
return self.cipher.encrypt(data)

def decrypt (self, data):
return self.cipher.decrypt(data)

def counter (self):
r = ('%032x' % (self.counter_value,)).decode ('hex')
self.counter_value += 1
return r

def set_encryption_key_and_iv (self, key, IV):
self.key = key
self.IV = IV
self.counter_value = int (IV.encode ('hex'), 16)
self.cipher = AES.new (key, AES.MODE_CTR, IV, counter=self.counter)
2 changes: 1 addition & 1 deletion coro/ssh/cipher/blowfish_cbc.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class Blowfish_CBC(SSH_Cipher_Method):
def encrypt(self, data):
return self.cipher.encrypt(data)

def descrypt(self, data):
def decrypt(self, data):
return self.cipher.decrypt(data)

def set_encryption_key_and_iv(self, key, IV):
Expand Down
20 changes: 17 additions & 3 deletions coro/ssh/connection/connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,24 @@ def register_channel(self, channel):
self.local_channels[channel.channel_id] = channel

def msg_global_request(self, pkt):
# XXX: finish this (it's a server thing)
data, offset = ssh_packet.unpack_payload_get_offset(SSH_MSG_GLOBAL_REQUEST_PAYLOAD, pkt)
msg, request_name, want_reply = data
raise NotImplementedError
_, request_name, want_reply = data
self.transport.debug.write (
ssh_debug.DEBUG_3,
'global_request: request_name=%r want_reply=%r pkt=%r', (request_name, want_reply, pkt)
)
# Note: GLOBAL_REQUEST is a general mechanism, previously raising NotImplementedError here.
# https://github.com/openssh/openssh-portable/blob/master/PROTOCOL
if request_name == '[email protected]':
# XXX this needs a proper implementation.
self.transport.debug.write (ssh_debug.WARNING, 'ignoring [email protected]')
if want_reply:
self.transport.send (SSH_MSG_GLOBAL_REQUEST_FAILURE_PAYLOAD, (SSH_MSG_GLOBAL_REQUEST_FAILURE,))
else:
self.transport.send (SSH_MSG_GLOBAL_REQUEST_SUCCESS_PAYLOAD, (SSH_MSG_GLOBAL_REQUEST_SUCCESS,))
else:
# fail all other requests
self.transport.send (SSH_MSG_GLOBAL_REQUEST_FAILURE_PAYLOAD, (SSH_MSG_GLOBAL_REQUEST_FAILURE,))

def msg_channel_window_adjust(self, pkt):
msg, channel_id, bytes_to_add = ssh_packet.unpack_payload(SSH_MSG_CHANNEL_WINDOW_ADJUST_PAYLOAD, pkt)
Expand Down
4 changes: 2 additions & 2 deletions coro/ssh/connection/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@
packet.BOOLEAN) # want reply

# This may contain additional request-specific data.
SSH_MSG_GLOBAL_REQUEST_SUCCESS_PAYLOAD = (packet.BYTE) # SSH_MSG_GLOBAL_REQUEST_SUCCESS
SSH_MSG_GLOBAL_REQUEST_SUCCESS_PAYLOAD = (packet.BYTE,) # SSH_MSG_GLOBAL_REQUEST_SUCCESS

SSH_MSG_GLOBAL_REQUEST_FAILURE_PAYLOAD = (packet.BYTE) # SSH_MSG_GLOBAL_REQUEST_FAILURE
SSH_MSG_GLOBAL_REQUEST_FAILURE_PAYLOAD = (packet.BYTE,) # SSH_MSG_GLOBAL_REQUEST_FAILURE

SSH_MSG_CHANNEL_WINDOW_ADJUST_PAYLOAD = (packet.BYTE, # SSH_MSG_CHANNEL_WINDOW_ADJUST
packet.UINT32, # recipient channel
Expand Down
1 change: 1 addition & 0 deletions coro/ssh/crypto/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# empty
86 changes: 86 additions & 0 deletions coro/ssh/crypto/curve25519.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# -*- Mode: Python -*-

# based on code from here:
# https://code.google.com/p/iphone-dataprotection/
# in python_scripts/crypto/curve25519

# based on PyCrypto.Util.number.inverse
def inverse (u0, v0):
u3, v3 = u0, v0
u1, v1 = 1, 0
while v3 > 0:
q, _ = divmod (u3, v3)
u1, v1 = v1, u1 - v1*q
u3, v3 = v3, u3 - v3*q
while u1 < 0:
u1 = u1 + v0
return u1

def b2n (s):
# little-endian, bytes -> number
return int (s[::-1].encode ('hex'), 16)

def n2b (n, reverse=False):
# little-endian, number -> bytes
return ('%064x' % n).decode ('hex')[::-1]

P = 2**255 - 19
# this is actually (A-2)/4
A = 121665

def curve25519_mult(n, q):

def monty (x1, z1, x2, z2, qmqp):
a = (x1 + z1) * (x2 - z2) % P
b = (x1 - z1) * (x2 + z2) % P
x4 = (a + b) * (a + b) % P
e = (a - b) * (a - b) % P
z4 = e * qmqp % P
a = (x1 + z1) * (x1 + z1) % P
b = (x1 - z1) * (x1 - z1) % P
x3 = a * b % P
g = (a - b) % P
h = (a + A * g) % P
z3 = (g * h) % P
return x3, z3, x4, z4

nqpqx, nqpqz = q, 1
nqx, nqz = 1, 0
for i in range (255, -1, -1):
if (n >> i) & 1:
nqpqx, nqpqz, nqx, nqz = monty (nqpqx, nqpqz, nqx, nqz, q)
else:
nqx, nqz, nqpqx, nqpqz = monty (nqx, nqz, nqpqx, nqpqz, q)

return (nqx * inverse (nqz, P)) % P

def adjust (k):
a = ord(k[0])
a &= 248
b = ord(k[31])
b &= 127
b |= 64
return chr(a) + k[1:-1] + chr(b)

def curve25519 (sk, pk):
skn = b2n (adjust (sk))
pkn = b2n (pk)
return n2b (curve25519_mult (skn, pkn))

def gen_key():
import os
sk = adjust (os.urandom (32))
return sk, curve25519 (sk, n2b (9))

# --------------------------------------------------------------------------------

def test():
k0 = "62ca760c588569cc6cdaea397ddfbe8d0a04ce69c141391abbdd24f5f473f6ab".decode ('hex')
k1 = "1cb967a181bb05edcdd2b82e1d3bcd10bf6a83739065e247ab0798d21e387520".decode ('hex')
p0 = "162e9fac9a1ec8853463a342d125a7d378848050030d06588e5ff7d67c828e5a".decode ('hex')
p1 = "cb624d49243142ad79452e8f37ae77fb1235609a8336c087429a2a3998681b21".decode ('hex')
assert curve25519 (k0, p1) == curve25519 (k1, p0)
assert curve25519 (k0, n2b (9)) == p0

if __name__ == '__main__':
test()
108 changes: 108 additions & 0 deletions coro/ssh/crypto/ed25519.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# -*- Mode: Python -*-

# http://ed25519.cr.yp.to/python/ed25519.py

import hashlib

b = 256
q = 2**255 - 19
l = 2**252 + 27742317777372353535851937790883648493

def H(m):
return hashlib.sha512(m).digest()

def expmod(b,e,m):
if e == 0: return 1
t = expmod(b,e/2,m)**2 % m
if e & 1: t = (t*b) % m
return t

def inv(x):
return expmod(x,q-2,q)

d = -121665 * inv(121666)
I = expmod(2,(q-1)/4,q)

def xrecover(y):
xx = (y*y-1) * inv(d*y*y+1)
x = expmod(xx,(q+3)/8,q)
if (x*x - xx) % q != 0: x = (x*I) % q
if x % 2 != 0: x = q-x
return x

By = 4 * inv(5)
Bx = xrecover(By)
B = [Bx % q,By % q]

def edwards(P,Q):
x1 = P[0]
y1 = P[1]
x2 = Q[0]
y2 = Q[1]
x3 = (x1*y2+x2*y1) * inv(1+d*x1*x2*y1*y2)
y3 = (y1*y2+x1*x2) * inv(1-d*x1*x2*y1*y2)
return [x3 % q,y3 % q]

def scalarmult(P,e):
if e == 0: return [0,1]
Q = scalarmult(P,e/2)
Q = edwards(Q,Q)
if e & 1: Q = edwards(Q,P)
return Q

def encodeint(y):
bits = [(y >> i) & 1 for i in range(b)]
return ''.join([chr(sum([bits[i * 8 + j] << j for j in range(8)])) for i in range(b/8)])

def encodepoint(P):
x = P[0]
y = P[1]
bits = [(y >> i) & 1 for i in range(b - 1)] + [x & 1]
return ''.join([chr(sum([bits[i * 8 + j] << j for j in range(8)])) for i in range(b/8)])

def bit(h,i):
return (ord(h[i/8]) >> (i%8)) & 1

def publickey(sk):
h = H(sk)
a = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2))
A = scalarmult(B,a)
return encodepoint(A)

def Hint(m):
h = H(m)
return sum(2**i * bit(h,i) for i in range(2*b))

def signature(m,sk,pk):
h = H(sk)
a = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2))
r = Hint(''.join([h[i] for i in range(b/8,b/4)]) + m)
R = scalarmult(B,r)
S = (r + Hint(encodepoint(R) + pk + m) * a) % l
return encodepoint(R) + encodeint(S)

def isoncurve(P):
x = P[0]
y = P[1]
return (-x*x + y*y - 1 - d*x*x*y*y) % q == 0

def decodeint(s):
return sum(2**i * bit(s,i) for i in range(0,b))

def decodepoint(s):
y = sum(2**i * bit(s,i) for i in range(0,b-1))
x = xrecover(y)
if x & 1 != bit(s,b-1): x = q-x
P = [x,y]
if not isoncurve(P): raise Exception("decoding point that is not on curve")
return P

def checkvalid(s,m,pk):
if len(s) != b/4: raise Exception("signature length is wrong")
if len(pk) != b/8: raise Exception("public-key length is wrong")
R = decodepoint(s[0:b/8])
A = decodepoint(pk)
S = decodeint(s[b/8:b/4])
h = Hint(encodepoint(R) + pk + m)
if scalarmult(B,S) != edwards(R,scalarmult(A,h)):
raise Exception("signature does not pass verification")
Loading