racket-ssh-2012/ssh-transport.rkt

140 lines
4.7 KiB
Racket
Raw Normal View History

2011-08-11 04:25:28 +00:00
#lang racket/base
(require (planet tonyg/bitsyntax))
(require (planet vyzo/crypto:2:3))
(require rackunit)
(require "safe-io.rkt")
(require "ssh-numbers.rkt")
(require "ssh-message-types.rkt")
(define identification-recogniser #rx"^SSH-")
(define (identification-line? str)
(regexp-match identification-recogniser str))
(define required-server-identification-regex (make-parameter #rx"^SSH-2\\.0-.*"))
(define client-preamble-lines (make-parameter '()))
(define client-identification-string (make-parameter "SSH-2.0-RacketSSH_0.0"))
(define (send-preamble-and-identification! out)
(for-each (lambda (line)
(when (identification-line? line)
(error 'ssh-session
"Client preamble includes forbidden line ~v"
line))
(display line out)
(display "\r\n" out))
(client-preamble-lines))
(display (client-identification-string) out)
(display "\r\n" out)
(flush-output out))
(define (read-preamble-and-identification! in)
(let ((line (read-line-limited in 253))) ;; 255 incl CRLF
(when (eof-object? line)
(error 'ssh-session "EOF while reading connection preamble"))
(if (identification-line? line)
line
(read-preamble-and-identification! in))))
(define (check-packet-length! actual-length limit block-size)
(when (> actual-length limit)
(error 'check-packet-length!
"Packet of length ~v is longer than packet limit ~v"
actual-length
limit))
(when (not (zero? (modulo actual-length block-size)))
(error 'check-packet-length!
"Packet of length ~v is not a multiple of block size ~v"
actual-length
block-size)))
(define (read-bytes/decrypt count in decryptor)
(let ((encrypted (read-bytes count in)))
(if (eof-object? encrypted)
encrypted
(decryptor encrypted))))
;; uint32 packet_length
;; byte padding_length
;; byte[n1] payload; n1 = packet_length - padding_length - 1
;; byte[n2] random padding; n2 = padding_length
;; byte[m] mac (Message Authentication Code - MAC); m = mac_length
(define (read-packet in
cipher
sequence-number
check-mac!
packet-size-limit)
(define first-block-size (if cipher (cipher-block-size cipher) 8))
(define subsequent-block-size (if cipher first-block-size 1))
(define decryptor (if cipher (lambda (blocks) (cipher-update! cipher blocks)) values))
(define first-block (read-bytes/decrypt first-block-size in decryptor))
(if (eof-object? first-block)
first-block
(let* ((packet-length (integer-bytes->integer first-block #f #t 0 4)))
(check-packet-length! packet-length packet-size-limit subsequent-block-size)
(let* ((padding-length (bytes-ref first-block 4))
(payload-length (- packet-length padding-length 1))
(remaining-to-read (- packet-length (bytes-length first-block))))
(define (read-packet-trailer packet)
(check-mac! sequence-number packet in)
(subbytes packet 5 (+ 5 payload-length)))
(if (positive? remaining-to-read)
(let ((trailing-blocks (read-bytes/decrypt remaining-to-read in decryptor)))
(if (eof-object? trailing-blocks)
trailing-blocks
(read-packet-trailer (bytes-append first-block trailing-blocks))))
(read-packet-trailer first-block))))))
(define (null-mac-checker sequence-number packet in)
'ok)
(define (ssh-session in out seed packet-handler)
(send-preamble-and-identification! out)
(let ((server-identification-string (read-preamble-and-identification! in)))
;; Each identification string is both a cleartext indicator that
;; we've reached some notion of the right place and also input to
;; the hash function used during D-H key exchange.
(when (not (regexp-match (required-server-identification-regex)
server-identification-string))
(error 'ssh-session "Received invalid identification ~v from peer"
server-identification-string))
(define (write-packet! . args)
(error 'write-packet "Unimplemented"))
(define result
(let loop ((seed seed)
(sequence-number 0)
(cipher #f)
(check-mac! null-mac-checker))
(let ((packet (read-packet in cipher sequence-number check-mac! 65536)))
(if (eof-object? packet)
seed
(packet-handler seed
packet
write-packet!
(lambda (new-seed) (loop new-seed
(bitwise-and (+ sequence-number 1)
#xffffffff)
2011-08-11 04:25:28 +00:00
cipher
check-mac!))
(lambda args
(error 'rekey "Unimplemented")))))))
(close-input-port in)
(close-output-port out)
result))
(require racket/tcp)
(require racket/pretty)
(define (t)
(let-values (((i o) (tcp-connect "localhost" 22)))
(ssh-session i o #f
(lambda (seed packet write-packet! continue-reading rekey)
(pretty-print packet)
(pretty-print (ssh-message-decode packet))
(continue-reading seed)))))
(t)