First steps toward modernization and port to syndicate-rkt.

Switch from (planet vyzo/crypto) to crypto.
Comment out most of the upper layers of the protocol.
Switch to new syndicate/rkt.

Unfortunately since I last ran this, the set of MUST-implement kex
methods has changed and there's no overlap with my default SSH client :-)
This commit is contained in:
Tony Garnock-Jones 2021-06-12 20:31:34 +02:00
parent 5381a0b8d3
commit 3c07c96307
13 changed files with 967 additions and 1562 deletions

View File

@ -1,66 +0,0 @@
#lang racket/base
;;; SPDX-License-Identifier: LGPL-3.0-or-later
;;; SPDX-FileCopyrightText: Copyright © 2011-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
;;; Provide AES CTR mode, since OpenSSL's EVP support for AES CTR mode
;;; is still ifdef'd out.
(provide start-aes-ctr
aes-ctr-process!)
(require ffi/unsafe)
(require ffi/unsafe/define)
(require openssl/libcrypto)
(define _AES_KEY-pointer _pointer)
(define AES_BLOCK_SIZE 16)
(define sizeof-AES_KEY 244) ;; TODO: figure out a good way to get this
;; from the header file or the library
;; itself
(define-ffi-definer define-crypto libcrypto
#:default-make-fail make-not-available)
(define-crypto AES_set_encrypt_key (_fun _pointer _int _AES_KEY-pointer -> _int))
;;(define-crypto AES_set_decrypt_key (_fun _pointer _int _AES_KEY-pointer -> _int))
(define-crypto AES_ctr128_encrypt
(_fun _pointer ;; in
_pointer ;; out
_long ;; length
_AES_KEY-pointer ;; key
_pointer ;; ivec, AES_BLOCK_SIZE bytes
_pointer ;; ecount_buf, AES_BLOCK_SIZE bytes
_pointer ;; int pointer, the "num" field of the ongoing state (??)
-> _void))
(struct aes-ctr-state (key ivec ecount num) #:transparent)
(define (start-aes-ctr key initialization-vector)
(let ((key-buffer (malloc sizeof-AES_KEY))
(ivec (make-bytes AES_BLOCK_SIZE))
(ecount (make-bytes AES_BLOCK_SIZE))
(num (make-bytes (ctype-sizeof _int))))
(AES_set_encrypt_key key
(* 8 (bytes-length key)) ;; measured in bits
key-buffer)
(bytes-copy! ivec 0 initialization-vector 0 AES_BLOCK_SIZE)
(bytes-fill! ecount 0)
(bytes-fill! num 0)
(aes-ctr-state key-buffer
ivec
ecount
num)))
(define (aes-ctr-process! state input-block)
(define block-length (bytes-length input-block))
(define output-block (make-bytes block-length))
(AES_ctr128_encrypt input-block
output-block
block-length
(aes-ctr-state-key state)
(aes-ctr-state-ivec state)
(aes-ctr-state-ecount state)
(aes-ctr-state-num state))
output-block)

9
syndicate-ssh/crypto.rkt Normal file
View File

@ -0,0 +1,9 @@
#lang racket/base
;;; SPDX-License-Identifier: LGPL-3.0-or-later
;;; SPDX-FileCopyrightText: Copyright © 2011-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
(provide (all-from-out crypto))
(require crypto)
(require crypto/all)
(use-all-factories!)

View File

@ -1,77 +0,0 @@
#lang racket/base
;;; SPDX-License-Identifier: LGPL-3.0-or-later
;;; SPDX-FileCopyrightText: Copyright © 2011-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
(provide make-queue
queue?
enqueue
enqueue-all
dequeue
list->queue
queue->list
queue-length
queue-empty?
queue-append
queue-extract)
(struct queue (head tail) #:transparent)
(define (make-queue)
(queue '() '()))
(define (enqueue q v)
(queue (queue-head q)
(cons v (queue-tail q))))
(define (enqueue-all q v)
(queue (queue-head q)
(append (reverse v) (queue-tail q))))
(define (shuffle q)
(if (null? (queue-head q))
(queue (reverse (queue-tail q)) '())
q))
(define (dequeue q)
(let ((q1 (shuffle q)))
(values (car (queue-head q1))
(queue (cdr (queue-head q1)) (queue-tail q1)))))
(define (list->queue xs)
(queue xs '()))
(define (queue->list q)
(append (queue-head q) (reverse (queue-tail q))))
(define (queue-length q)
(+ (length (queue-head q))
(length (queue-tail q))))
(define (queue-empty? q)
(and (null? (queue-head q))
(null? (queue-tail q))))
(define (queue-append q1 q2)
(queue (append (queue-head q1)
(reverse (queue-tail q1))
(queue-head q2))
(queue-tail q2)))
(define (queue-extract q predicate [default-value #f])
(let search-head ((head (queue-head q))
(rejected-head-rev '()))
(cond
((null? head) (let search-tail ((tail (reverse (queue-tail q)))
(rejected-tail-rev '()))
(cond
((null? tail) (values default-value q))
((predicate (car tail)) (values (car tail)
(queue (queue-head q)
(append (reverse (cdr tail))
rejected-tail-rev))))
(else (search-tail (cdr tail) (cons (car tail) rejected-tail-rev))))))
((predicate (car head)) (values (car head)
(queue (append (reverse rejected-head-rev)
(cdr head))
(queue-tail q))))
(else (search-head (cdr head) (cons (car head) rejected-head-rev))))))

View File

@ -9,11 +9,12 @@
"base" "base"
"bitsyntax" "bitsyntax"
"syndicate" "crypto"
"preserves" "preserves"
"syndicate"
)) ))
;; (define build-deps '("rackunit-lib")) (define build-deps '("rackunit-lib"))
(define pre-install-collection "private/install.rkt") (define pre-install-collection "private/install.rkt")

View File

@ -1,15 +0,0 @@
#lang racket/base
;;; SPDX-License-Identifier: LGPL-3.0-or-later
;;; SPDX-FileCopyrightText: Copyright © 2013-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
;;; Reexport racket-matrix module contents.
(require marketplace/sugar)
(require marketplace/drivers/tcp)
(require marketplace/drivers/timer)
(require marketplace/drivers/event-relay)
(provide (all-from-out marketplace/sugar))
(provide (all-from-out marketplace/drivers/tcp))
(provide (all-from-out marketplace/drivers/timer))
(provide (all-from-out marketplace/drivers/event-relay))

View File

@ -1,12 +1,13 @@
#lang racket/base #lang syndicate
;;; SPDX-License-Identifier: LGPL-3.0-or-later ;;; SPDX-License-Identifier: LGPL-3.0-or-later
;;; SPDX-FileCopyrightText: Copyright © 2012-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com> ;;; SPDX-FileCopyrightText: Copyright © 2012-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
;;; (Temporary) example client and server ;;; (Temporary) example client and server
(require racket/set) (require syndicate/drivers/timer)
(require racket/match) (require syndicate/drivers/tcp)
(require racket/contract) (require syndicate/dataspace)
(require (only-in racket/port peek-bytes-avail!-evt)) (require (only-in racket/port peek-bytes-avail!-evt))
(require "cook-port.rkt") (require "cook-port.rkt")
(require "sandboxes.rkt") (require "sandboxes.rkt")
@ -17,19 +18,16 @@
(require "ssh-channel.rkt") (require "ssh-channel.rkt")
(require "ssh-message-types.rkt") (require "ssh-message-types.rkt")
(require "ssh-exceptions.rkt") (require "ssh-exceptions.rkt")
(require "marketplace-support.rkt")
(define (main) (module+ main
(ground-vm (timer-driver) (actor-system/dataspace (ds)
(tcp-driver) (spawn-timer-driver ds)
(tcp-spy) (spawn-tcp-driver ds)
(name-process 'ssh-tcp-listener (spawn listener)))) (spawn #:name 'ssh-tcp-listener
(at ds
(define listener (during/spawn (Connection $conn (TcpInbound "0.0.0.0" 2322))
(transition/no-state #:name (list 'ssh conn)
(observe-publishers (tcp-channel ? (tcp-listener 2322) ?) (session ds conn))))))
(match-conversation r
(on-presence (session-vm r))))))
;;--------------------------------------------------------------------------- ;;---------------------------------------------------------------------------
@ -44,242 +42,164 @@
"Invalid peer identification string ~v" "Invalid peer identification string ~v"
peer-identification-string))) peer-identification-string)))
(define (spy marker) (define (session ground-ds conn)
(define (dump what message) (define root-facet this-facet)
(write `(,marker ,what ,message))
(newline)
(flush-output)
(void))
(list
(observe-publishers/everything (wild)
(match-interest-type i
(match-conversation c
(on-presence (dump 'arrived (role 'publisher c i)))
(on-absence (dump 'departed (role 'publisher c i)))
(on-message [message (dump 'message message)]))))
(observe-subscribers/everything (wild)
(match-interest-type i
(match-conversation c
(on-presence (dump 'arrived (role 'subscriber c i)))
(on-absence (dump 'departed (role 'subscriber c i)))
(on-message [message (dump 'feedback message)]))))))
(define-syntax-rule (wait-as my-orientation topic action ...) (define update-input-handler
(let-fresh (endpoint-name) (accept-connection conn
(build-endpoint endpoint-name #:initial-credit #f
(role my-orientation topic 'observer) #:on-data (lambda (input mode) (error 'session "Unexpected input"))))
(match-state state
(on-presence (sequence-actions (transition state
(delete-endpoint endpoint-name)
action ...)))))))
(define (session-vm new-conversation) (define local-identification "SSH-2.0-RacketSSH_0.0")
(match-define (tcp-channel remote-addr local-addr _) new-conversation) (send-line conn local-identification)
(define local-identification #"SSH-2.0-RacketSSH_0.0")
(define (issue-identification-string) (send-lines-credit conn 1 (LineMode-crlf))
(at-meta-level (update-input-handler
(send-message (tcp-channel local-addr remote-addr #:on-data (lambda (remote-identification _mode)
(bytes-append local-identification #"\r\n"))))) (check-remote-identification! remote-identification)
(define (read-handshake-and-become-reader) (define session-vm
(transition 'handshake-is-stateless ;; but, crucially, the ssh-reader proper isn't! (actor-group [(on-stop
(at-meta-level (stop-facet root-facet)
(name-endpoint 'socket-reader (log-info "Session VM for ~a closed" conn))]
(subscriber (tcp-channel remote-addr local-addr ?) (define conn-ds (dataspace #:name (gensym 'conn-ds)))
(match-state state
(on-message
[(tcp-channel _ _ (? eof-object?))
(transition state (quit))]
[(tcp-channel _ _ (? bytes? remote-identification))
(begin
(check-remote-identification! remote-identification)
(sequence-actions (transition state)
;; First, set the incoming mode to bytes.
(at-meta-level
(send-feedback (tcp-channel remote-addr local-addr (tcp-mode 'bytes))))
;; Then initialise the reader, switching to packet-reading mode.
(lambda (ignored-state) (ssh-reader new-conversation))
;; Finally, spawn the remaining processes and issue
;; the initial credit to the reader.
(name-process 'ssh-writer
;; TODO: canary: #:exit-signal? #t
(spawn (ssh-writer new-conversation)))
;; Wait for the reader and writer get started, then tell
;; the reader we are ready for a single packet and spawn
;; the session manager.
(wait-as 'subscriber (inbound-packet (wild) (wild) (wild) (wild))
(wait-as 'publisher (outbound-packet (wild))
(send-message (inbound-credit 1))
(name-process 'ssh-session
(spawn #:pid session-pid
;; TODO: canary: #:exit-signal? #t
(ssh-session session-pid
local-identification
remote-identification
repl-boot
'server)))))))])))))))
(define (exn->outbound-packet reason) (spawn/link #:name 'reader
(outbound-packet (ssh-msg-disconnect (exn:fail:contract:protocol-reason-code reason) (ssh-reader conn-ds conn update-input-handler))
(string->bytes/utf-8 (exn-message reason)) (spawn/link #:name 'writer
#""))) (ssh-writer conn-ds conn))
(define (disconnect-message-required? reason) ;; Wait for the reader and writer get started, then tell the reader
(and (exn:fail:contract:protocol? reason) ;; we are ready for a single packet and spawn the session manager.
(not (exn:fail:contract:protocol-originated-at-peer? reason)))) (react
(at conn-ds
(stop-when (asserted (Observe (:pattern (inbound-credit ,_)) _))
(send! conn-ds (inbound-credit 1))
(define (active-exception-handler reason) (spawn/link
;; This is kind of gross: because the absence handler gets invoked #:name 'session
;; several times in a row because of multiple flows intersecting (ssh-session conn-ds
;; this role, we have to be careful to make the transmission of ground-ds
;; the disconnection packet idempotent. local-identification
;; TODO: this is likely no longer true now we're using exit-signals %%% remote-identification
(define interesting? (disconnect-message-required? reason)) (lambda (user-name)
(transition inert-exception-handler (error 'repl-boot "Would start session with ~a" user-name))
(when interesting? (send-message (exn->outbound-packet reason))) 'server)))))
(yield state ;; gross
(transition state (at-meta-level (quit #f (and interesting? reason)))))))
(define (inert-exception-handler reason) (at conn-ds
inert-exception-handler) (during $m
(on-start (log-info "++ ~v" m))
(on-stop (log-info "-- ~v" m)))
(when (message $m)
(log-info ">> ~v" m)))
(spawn-vm #:debug-name (list 'ssh-session-vm new-conversation) (at conn-ds
(event-relay 'ssh-event-relay) (when (asserted (protocol-error $reason-code $message _ $originated-at-peer?))
(timer-relay 'ssh-timer-relay) (when (not originated-at-peer?)
(spy 'SSH) (send! conn-ds
(outbound-packet (ssh-msg-disconnect reason-code
(string->bytes/utf-8 message)
#""))))
(actor-system-shutdown! session-vm)))))
(issue-identification-string) (void))))
;; Expect identification string, then update (!) our inbound
;; subscription handler to switch to packet mode.
(at-meta-level
(send-feedback (tcp-channel remote-addr local-addr (tcp-mode 'lines)))
(send-feedback (tcp-channel remote-addr local-addr (tcp-credit 1))))
(name-process 'ssh-reader
;; TODO: canary: #:exit-signal? #t
(spawn (read-handshake-and-become-reader)))
;; TODO: canary:
;; (spawn #:child
;; (transition active-exception-handler
;; (role (topic-subscriber (exit-signal (wild) (wild)))
;; #:state current-handler
;; #:reason reason
;; #:on-absence (current-handler reason))))
))
;;--------------------------------------------------------------------------- ;;---------------------------------------------------------------------------
(define (repl-boot user-name) ;; ;; (repl-instance InputPort OutputPort InputPort OutputPort)
(list ;; (struct repl-instance-state (c2s-in ;; used by thread to read input from relay
(event-relay 'app-event-relay) ;; c2s-out ;; used by relay to feed input from remote to the thread
(spy 'APP) ;; s2c-in ;; used by relay to feed output from thread to remote
(at-meta-level ;; s2c-out ;; used by thread to write output to relay
(subscriber (channel-message (channel-stream-name #t (wild)) (wild)) ;; ) #:prefab)
(match-conversation (channel-message (channel-stream-name _ cname) _)
(on-presence (name-process cname (spawn (repl-instance user-name cname)))))))))
;; (repl-instance InputPort OutputPort InputPort OutputPort) ;; (define (repl-instance user-name cname)
(struct repl-instance-state (c2s-in ;; used by thread to read input from relay ;; (define inbound-stream (channel-stream-name #t cname))
c2s-out ;; used by relay to feed input from remote to the thread ;; (define outbound-stream (channel-stream-name #f cname))
s2c-in ;; used by relay to feed output from thread to remote ;; (define (ch-do action-ctor stream body)
s2c-out ;; used by thread to write output to relay ;; (at-meta-level (action-ctor (channel-message stream body))))
) #:prefab) ;; (define (handle-channel-message state body)
;; (match body
(define (repl-instance user-name cname) ;; [(channel-stream-request #"pty-req" _)
(define inbound-stream (channel-stream-name #t cname)) ;; (match-define (repl-instance-state old-in _ _ old-out) state)
(define outbound-stream (channel-stream-name #f cname)) ;; (define-values (cooked-in cooked-out) (cook-io old-in old-out "> "))
(define (ch-do action-ctor stream body) ;; (transition (struct-copy repl-instance-state state
(at-meta-level (action-ctor (channel-message stream body)))) ;; [c2s-in cooked-in]
(define (handle-channel-message state body) ;; [s2c-out cooked-out])
(match body ;; (ch-do send-feedback inbound-stream (channel-stream-ok)))]
[(channel-stream-request #"pty-req" _) ;; [(channel-stream-notify #"env" _)
(match-define (repl-instance-state old-in _ _ old-out) state) ;; ;; Don't care
(define-values (cooked-in cooked-out) (cook-io old-in old-out "> ")) ;; (transition state)]
(transition (struct-copy repl-instance-state state ;; [(channel-stream-request #"shell" _)
[c2s-in cooked-in] ;; (match-define (repl-instance-state c2s-in _ s2c-in s2c-out) state)
[s2c-out cooked-out]) ;; (define buffer-size 1024)
(ch-do send-feedback inbound-stream (channel-stream-ok)))] ;; (define dummy-buffer (make-bytes buffer-size))
[(channel-stream-notify #"env" _) ;; (define repl-thread (thread (lambda () (repl-shell user-name c2s-in s2c-out))))
;; Don't care ;; (transition state
(transition state)] ;; (ch-do send-feedback inbound-stream (channel-stream-ok))
[(channel-stream-request #"shell" _) ;; (subscriber (cons (thread-dead-evt repl-thread) (wild))
(match-define (repl-instance-state c2s-in _ s2c-in s2c-out) state) ;; (on-message [_ (quit #f "REPL thread exited")]))
(define buffer-size 1024) ;; (subscriber (cons (peek-bytes-avail!-evt dummy-buffer 0 #f s2c-in) (wild))
(define dummy-buffer (make-bytes buffer-size)) ;; ;; We're using peek-bytes-avail!-evt rather than
(define repl-thread (thread (lambda () (repl-shell user-name c2s-in s2c-out)))) ;; ;; read-bytes-avail!-evt because of potential overwriting
(transition state ;; ;; of the buffer. The overwriting can happen when there's
(ch-do send-feedback inbound-stream (channel-stream-ok)) ;; ;; any latency between handling the event and the next
(subscriber (cons (thread-dead-evt repl-thread) (wild)) ;; ;; firing of the event, since the peek-bytes-avail!-evt
(on-message [_ (quit #f "REPL thread exited")])) ;; ;; will overwrite its buffer next time it's synced on.
(subscriber (cons (peek-bytes-avail!-evt dummy-buffer 0 #f s2c-in) (wild)) ;; (match-state state
;; We're using peek-bytes-avail!-evt rather than ;; (on-message
;; read-bytes-avail!-evt because of potential overwriting ;; [(cons _ (? eof-object?))
;; of the buffer. The overwriting can happen when there's ;; (let ()
;; any latency between handling the event and the next ;; (match-define (repl-instance-state c2s-in c2s-out s2c-in s2c-out) state)
;; firing of the event, since the peek-bytes-avail!-evt ;; (close-input-port c2s-in)
;; will overwrite its buffer next time it's synced on. ;; (close-output-port c2s-out)
(match-state state ;; (close-input-port s2c-in)
(on-message ;; (close-output-port s2c-out)
[(cons _ (? eof-object?)) ;; (transition state (quit)))]
(let () ;; [(cons _ (? number? count))
(match-define (repl-instance-state c2s-in c2s-out s2c-in s2c-out) state) ;; (transition state
(close-input-port c2s-in) ;; (ch-do send-message outbound-stream (channel-stream-data
(close-output-port c2s-out) ;; (read-bytes count s2c-in))))]))))]
(close-input-port s2c-in) ;; [(or (channel-stream-data #"\4") ;; C-d a.k.a EOT
(close-output-port s2c-out) ;; (channel-stream-eof))
(transition state (quit)))] ;; (let ()
[(cons _ (? number? count)) ;; (close-output-port (repl-instance-state-c2s-out state))
(transition state ;; ;; ^ this signals the repl thread to exit.
(ch-do send-message outbound-stream (channel-stream-data ;; ;; Now, wait for it to do so.
(read-bytes count s2c-in))))]))))] ;; (transition state))]
[(or (channel-stream-data #"\4") ;; C-d a.k.a EOT ;; [(channel-stream-data bs)
(channel-stream-eof)) ;; (write-bytes bs (repl-instance-state-c2s-out state))
(let () ;; (flush-output (repl-instance-state-c2s-out state))
(close-output-port (repl-instance-state-c2s-out state)) ;; (transition state
;; ^ this signals the repl thread to exit. ;; (ch-do send-feedback inbound-stream (channel-stream-credit (bytes-length bs))))]
;; Now, wait for it to do so. ;; [m
(transition state))] ;; (write `(channel inbound ,m)) (newline)
[(channel-stream-data bs) ;; (transition state)]))
(write-bytes bs (repl-instance-state-c2s-out state)) ;; (match (channel-name-type cname)
(flush-output (repl-instance-state-c2s-out state)) ;; [#"session"
(transition state ;; (define-values (c2s-in c2s-out) (make-pipe))
(ch-do send-feedback inbound-stream (channel-stream-credit (bytes-length bs))))] ;; (define-values (s2c-in s2c-out) (make-pipe))
[m ;; (transition (repl-instance-state c2s-in c2s-out s2c-in s2c-out)
(write `(channel inbound ,m)) (newline) ;; (at-meta-level
(transition state)])) ;; (subscriber (channel-message inbound-stream (wild))
(match (channel-name-type cname) ;; (match-state state
[#"session" ;; (on-presence (transition state
(define-values (c2s-in c2s-out) (make-pipe)) ;; (ch-do send-feedback inbound-stream (channel-stream-config
(define-values (s2c-in s2c-out) (make-pipe)) ;; (default-packet-limit)
(transition (repl-instance-state c2s-in c2s-out s2c-in s2c-out) ;; #""))
(at-meta-level ;; (ch-do send-feedback inbound-stream (channel-stream-credit 1024))))
(subscriber (channel-message inbound-stream (wild)) ;; (on-message
(match-state state ;; [(channel-message _ body)
(on-presence (transition state ;; (handle-channel-message state body)]))))
(ch-do send-feedback inbound-stream (channel-stream-config ;; (at-meta-level
(default-packet-limit) ;; (publisher (channel-message outbound-stream (wild))
#"")) ;; (on-message [m (begin
(ch-do send-feedback inbound-stream (channel-stream-credit 1024)))) ;; (write `(channel outbound ,cname ,m)) (newline)
(on-message ;; (void))]))))]
[(channel-message _ body) ;; [type
(handle-channel-message state body)])))) ;; (transition/no-state
(at-meta-level ;; (at-meta-level (send-message
(publisher (channel-message outbound-stream (wild)) ;; (channel-message outbound-stream
(on-message [m (begin ;; (channel-stream-open-failure
(write `(channel outbound ,cname ,m)) (newline) ;; SSH_OPEN_UNKNOWN_CHANNEL_TYPE
(void))]))))] ;; (bytes-append #"Unknown channel type " type))))))]))
[type
(transition/no-state
(at-meta-level (send-message
(channel-message outbound-stream
(channel-stream-open-failure
SSH_OPEN_UNKNOWN_CHANNEL_TYPE
(bytes-append #"Unknown channel type " type))))))]))
;;---------------------------------------------------------------------------
;; TODO: module+
(main)

View File

@ -7,24 +7,24 @@
(provide dh:oakley-group-2 (provide dh:oakley-group-2
dh:oakley-group-14) dh:oakley-group-14)
;;(require (planet vyzo/crypto)) (require "crypto.rkt")
(require (planet vyzo/crypto/dh))
(require (only-in net/base64 base64-decode)) (require (only-in net/base64 base64-decode))
(define dh:oakley-group-2 (define dh:oakley-group-2
(make-!dh (datum->pk-parameters
1024
(base64-decode (base64-decode
#"MIGHAoGBAP//////////yQ/aoiFowjTExmKLgNwc0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE #"MIGHAoGBAP//////////yQ/aoiFowjTExmKLgNwc0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE
3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHCReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/ta 3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHCReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/ta
iZ+lrp8kEXxLH+ZJKGZR7OZTgf//////////AgEC"))) iZ+lrp8kEXxLH+ZJKGZR7OZTgf//////////AgEC")
'DHParameter))
(define dh:oakley-group-14 (define dh:oakley-group-14
(make-!dh (datum->pk-parameters
2048
(base64-decode (base64-decode
#"MIIBCAKCAQEA///////////JD9qiIWjCNMTGYouA3BzRKQJOCIpnzHQCC76mOxObIlFKCHmO #"MIIBCAKCAQEA///////////JD9qiIWjCNMTGYouA3BzRKQJOCIpnzHQCC76mOxObIlFKCHmO
NATd75UZs806QxswKwpt8l8UN0/hNW1tUcJF5IW1dmJefsb0TELppjftawv/XLb0Brft7jhr NATd75UZs806QxswKwpt8l8UN0/hNW1tUcJF5IW1dmJefsb0TELppjftawv/XLb0Brft7jhr
+1qJn6WunyQRfEsf5kkoZlHs5Fs9wgB8uKFjvwWY2kg2HFXTmmkWP6j9JM9fg2VdI9yjrZYc +1qJn6WunyQRfEsf5kkoZlHs5Fs9wgB8uKFjvwWY2kg2HFXTmmkWP6j9JM9fg2VdI9yjrZYc
YvNWIIVSu57VKQdwlpZtZww1Tkq8mATxdGwIyhghfDKQXkYuNs474553LBgOhgObJ4Oi7Aei YvNWIIVSu57VKQdwlpZtZww1Tkq8mATxdGwIyhghfDKQXkYuNs474553LBgOhgObJ4Oi7Aei
j7XFXfBvTFLJ3ivL9pVYFxg5lUl86pVq5RXSJhiY+gUQFXKOWoqsqmj//////////wIBAg=="))) j7XFXfBvTFLJ3ivL9pVYFxg5lUl86pVq5RXSJhiY+gUQFXKOWoqsqmj//////////wIBAg==")
'DHParameter))

View File

@ -1,44 +1,39 @@
#lang racket/base #lang syndicate
;;; SPDX-License-Identifier: LGPL-3.0-or-later ;;; SPDX-License-Identifier: LGPL-3.0-or-later
;;; SPDX-FileCopyrightText: Copyright © 2011-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com> ;;; SPDX-FileCopyrightText: Copyright © 2011-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
;;; Exceptions and error-raising and -handling utilities used in structuring SSH sessions. ;;; Error-raising and -handling utilities used in structuring SSH sessions.
(provide (struct-out exn:fail:contract:protocol) (provide (struct-out protocol-error)
disconnect-with-error disconnect-with-error
disconnect-with-error/local-info disconnect-with-error/local-info
disconnect-with-error*) disconnect-with-error*)
;; An exn:fail:contract:protocol, when thrown by the transport (TODO: ;; A `protocol-error`, when asserted, will cause a SSH_MSG_DISCONNECT
;; clarify scope of this behaviour) will cause a SSH_MSG_DISCONNECT to ;; to be sent to the remote party with the included reason code, using
;; be sent to the remote party with the included reason code, using ;; `message` as the description. The `local-info` field is useful
;; the exn-message as the description. The local-info field is useful
;; information for diagnosing problems known to the local stack that ;; information for diagnosing problems known to the local stack that
;; should not be transmitted to the remote party. For example, upon ;; should not be transmitted to the remote party. For example, upon
;; detection of a MAC failure, it might be useful to know the expected ;; detection of a MAC failure, it might be useful to know the expected
;; and actual MACs for debugging, but they should not be sent over the ;; and actual MACs for debugging, but they should not be sent over the
;; wire because we could be experiencing some kind of attack. ;; wire because we could be experiencing some kind of attack.
(struct exn:fail:contract:protocol exn:fail:contract (struct protocol-error (reason-code message local-info originated-at-peer?) #:prefab)
(reason-code local-info originated-at-peer?)
#:transparent)
;; Natural FormatString [Any ...] -> raised exn:fail:contract:protocol ;; DS Natural FormatString [Any ...] -> signalled protocol-error
(define (disconnect-with-error reason-code format-string . args) (define (disconnect-with-error ds reason-code format-string . args)
(apply disconnect-with-error* #f '() reason-code format-string args)) (apply disconnect-with-error* ds #f '() reason-code format-string args))
;; Any Natural FormatString [Any ...] -> raised exn:fail:contract:protocol ;; DS Any Natural FormatString [Any ...] -> signalled protocol-error
(define (disconnect-with-error/local-info local-info reason-code format-string . args) (define (disconnect-with-error/local-info ds local-info reason-code format-string . args)
(apply disconnect-with-error* #f local-info reason-code format-string args)) (apply disconnect-with-error* ds #f local-info reason-code format-string args))
;; Boolean Any Natural FormatString [Any ...] -> raised exn:fail:contract:protocol ;; DS Boolean Any Natural FormatString [Any ...] -> signalled protocol-error
(define (disconnect-with-error* originated-at-peer? (define (disconnect-with-error* ds
originated-at-peer?
local-info local-info
reason-code reason-code
format-string format-string
. args) . args)
(let ((message (apply format format-string args))) (define message (apply format format-string args))
(raise (exn:fail:contract:protocol message (spawn (at ds (assert (protocol-error reason-code message local-info originated-at-peer?))))
(current-continuation-marks) (error 'protocol-error "(~a) ~a: ~v" reason-code message local-info))
reason-code
local-info
originated-at-peer?))))

View File

@ -6,8 +6,8 @@
(require racket/port) (require racket/port)
(require net/base64) (require net/base64)
(require (planet vyzo/crypto))
(require bitsyntax) (require bitsyntax)
(require "crypto.rkt")
(require "asn1-ber.rkt") (require "asn1-ber.rkt")
(require "ssh-message-types.rkt") (require "ssh-message-types.rkt")
@ -22,7 +22,6 @@
pieces->public-key pieces->public-key
host-key-algorithm->keys host-key-algorithm->keys
host-key-algorithm->digest-type
host-key-signature host-key-signature
verify-host-key-signature! verify-host-key-signature!
@ -38,8 +37,8 @@
(define (bs->n bs) (bit-string->integer bs #t #t)) (define (bs->n bs) (bit-string->integer bs #t #t))
(define (n->bs n) (integer->bit-string n (* 8 (mpint-width n)) #t)) (define (n->bs n) (integer->bit-string n (* 8 (mpint-width n)) #t))
(define (private-key->pieces key) ;; (define (private-key->pieces key)
(bytes->private-key-pieces (private-key->bytes key))) ;; (bytes->private-key-pieces (private-key->bytes key)))
(define (bytes->private-key-pieces bs) (define (bytes->private-key-pieces bs)
(match (asn1-ber-decode-all bs) (match (asn1-ber-decode-all bs)
@ -76,65 +75,32 @@
(define (pieces->private-key p) (define (pieces->private-key p)
(match p (match p
((struct rsa-private-key (version n e d p q dmp1 dmq1 iqmp)) [(struct rsa-private-key (version n e d _p _q _dmp1 _dmq1 _iqmp))
(bytes->private-key pkey:rsa (datum->pk-key (list 'rsa 'private n e d)
(asn1-ber-encode `(0 16 ((0 2 ,(n->bs version)) 'rkt-private)]
(0 2 ,(n->bs n)) [(struct dsa-private-key (version p q g y x))
(0 2 ,(n->bs e)) (datum->pk-key (list 'dsa 'private p q g y x)
(0 2 ,(n->bs d)) 'rkt-private)]))
(0 2 ,(n->bs p))
(0 2 ,(n->bs q))
(0 2 ,(n->bs dmp1))
(0 2 ,(n->bs dmq1))
(0 2 ,(n->bs iqmp)))))))
((struct dsa-private-key (version p q g y x))
(bytes->private-key pkey:dsa
(asn1-ber-encode `(0 16 ((0 2 ,(n->bs version))
(0 2 ,(n->bs p))
(0 2 ,(n->bs q))
(0 2 ,(n->bs g))
(0 2 ,(n->bs y))
(0 2 ,(n->bs x)))))))))
(define (public-key->pieces key) (define (public-key->pieces key)
(match (asn1-ber-decode-all (public-key->bytes key)) (match (pk-key->datum key 'rkt-public)
(`(0 16 ((0 2 ,n-bytes) [(list 'rsa 'public n e)
(0 2 ,e-bytes))) (rsa-public-key n e)]
(rsa-public-key (bs->n n-bytes) [(list 'dsa 'public p q g public-key-bytes)
(bs->n e-bytes))) (dsa-public-key public-key-bytes p q g)]))
(`(0 16 ((0 2 ,public-key-bytes) ;; y
(0 2 ,p-bytes)
(0 2 ,q-bytes)
(0 2 ,g-bytes)))
(dsa-public-key (bs->n public-key-bytes)
(bs->n p-bytes)
(bs->n q-bytes)
(bs->n g-bytes)))))
(define (pieces->public-key p) (define (pieces->public-key p)
(match p (match p
((struct rsa-public-key (n e)) ((struct rsa-public-key (n e))
(bytes->public-key pkey:rsa (datum->pk-key (list 'rsa 'public n e) 'rkt-public))
(asn1-ber-encode `(0 16 ((0 2 ,(n->bs n))
(0 2 ,(n->bs e)))))))
((struct dsa-public-key (y p q g)) ((struct dsa-public-key (y p q g))
(bytes->public-key pkey:dsa (datum->pk-key (list 'dsa 'public p q g y) 'rkt-public))))
(asn1-ber-encode `(0 16 ((0 2 ,(n->bs y))
(0 2 ,(n->bs p))
(0 2 ,(n->bs q))
(0 2 ,(n->bs g)))))))))
(define (host-key-algorithm->keys host-key-alg) (define (host-key-algorithm->keys host-key-alg)
(case host-key-alg (case host-key-alg
((ssh-dss) (values host-key-dsa-private host-key-dsa-public)) ((ssh-dss) (values host-key-dsa-private host-key-dsa-public))
(else (error 'host-key-algorithm->keys "Unsupported host-key-alg ~v" host-key-alg)))) (else (error 'host-key-algorithm->keys "Unsupported host-key-alg ~v" host-key-alg))))
(define (host-key-algorithm->digest-type host-key-alg)
(case host-key-alg
((ssh-rsa) digest:sha1)
((ssh-dss) digest:dss1)
(else (error 'host-key-algorithm->digest-type "Unsupported host-key-alg ~v" host-key-alg))))
(define (host-key-signature private-key host-key-alg exchange-hash) (define (host-key-signature private-key host-key-alg exchange-hash)
(case host-key-alg (case host-key-alg
((ssh-rsa) ((ssh-rsa)
@ -142,7 +108,7 @@
;; local-algorithm-list in ssh-transport.rkt. ;; local-algorithm-list in ssh-transport.rkt.
(error 'host-key-signature "ssh-rsa host key signatures unimplemented")) (error 'host-key-signature "ssh-rsa host key signatures unimplemented"))
((ssh-dss) ((ssh-dss)
(match (asn1-ber-decode-all (sign private-key digest:dss1 exchange-hash)) (match (asn1-ber-decode-all (pk-sign private-key exchange-hash))
(`(0 16 ((0 2 ,r-bytes) (`(0 16 ((0 2 ,r-bytes)
(0 2 ,s-bytes))) (0 2 ,s-bytes)))
(bit-string (#"ssh-dss" :: (t:string)) (bit-string (#"ssh-dss" :: (t:string))
@ -167,7 +133,7 @@
(s :: big-endian integer bits 160) ] (s :: big-endian integer bits 160) ]
(asn1-ber-encode `(0 16 ((0 2 ,(n->bs r)) (asn1-ber-encode `(0 16 ((0 2 ,(n->bs r))
(0 2 ,(n->bs s)))))))))) (0 2 ,(n->bs s))))))))))
(when (not (verify public-key digest:dss1 signature exchange-hash)) (when (not (pk-verify public-key exchange-hash signature))
(error 'verify-host-key-signature! "Signature mismatch"))))) (error 'verify-host-key-signature! "Signature mismatch")))))
(define (pieces->ssh-host-key pieces) (define (pieces->ssh-host-key pieces)
@ -207,7 +173,8 @@
#""))))) #"")))))
(define host-key-dsa-private (load-private-key "test-dsa-key")) (define host-key-dsa-private (load-private-key "test-dsa-key"))
(define host-key-dsa-public (pkey->public-key host-key-dsa-private)) (define host-key-dsa-public (pk-key->public-only-key host-key-dsa-private))
(check-equal? (public-key->bytes (pieces->public-key (public-key->pieces host-key-dsa-public))) (check-equal? (pk-key->datum (pieces->public-key (public-key->pieces host-key-dsa-public))
(public-key->bytes host-key-dsa-private)) 'SubjectPublicKeyInfo)
(pk-key->datum host-key-dsa-private 'SubjectPublicKeyInfo))

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +1,16 @@
#lang racket/base #lang syndicate
;;; SPDX-License-Identifier: LGPL-3.0-or-later ;;; SPDX-License-Identifier: LGPL-3.0-or-later
;;; SPDX-FileCopyrightText: Copyright © 2011-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com> ;;; SPDX-FileCopyrightText: Copyright © 2011-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
(require bitsyntax) (require bitsyntax)
(require (planet vyzo/crypto:2:3))
(require racket/set)
(require racket/match)
(require rackunit) (require rackunit)
(require syndicate/drivers/tcp)
(require "aes-ctr.rkt") (require "crypto.rkt")
(require "ssh-numbers.rkt") (require "ssh-numbers.rkt")
(require "ssh-message-types.rkt") (require "ssh-message-types.rkt")
(require "ssh-exceptions.rkt") (require "ssh-exceptions.rkt")
(require "marketplace-support.rkt")
(provide (struct-out inbound-packet) (provide (struct-out inbound-packet)
(struct-out inbound-credit) (struct-out inbound-credit)
(struct-out outbound-packet) (struct-out outbound-packet)
@ -103,56 +96,42 @@
0 0
0)) 0))
(define (make-evp-cipher-entry name cipher) (define (make-cipher-entry name cipher-spec key-length)
(list name (list name
(supported-cipher name (supported-cipher name
(lambda (enc? key iv) (lambda (enc? key iv)
(let ((state ((if enc? cipher-encrypt cipher-decrypt) (lambda (input)
cipher key iv #:padding #f))) ((if enc? encrypt decrypt)
(lambda (block) cipher-spec key iv input #:pad #f)))
(cipher-update! state block)))) key-length
(cipher-key-length cipher) (cipher-block-size cipher-spec)
(cipher-block-size cipher) (cipher-iv-size cipher-spec))))
(cipher-iv-length cipher))))
(define (aes-ctr-cipher-factory enc? key iv)
(let ((state (start-aes-ctr key iv)))
(lambda (block)
(aes-ctr-process! state block))))
(define (make-aes-ctr-entry name key-length)
(list name
(supported-cipher name
aes-ctr-cipher-factory
key-length
16
16)))
(define supported-crypto-algorithms (define supported-crypto-algorithms
(list (list
(make-aes-ctr-entry 'aes128-ctr 16) (make-cipher-entry 'aes128-ctr '(aes ctr) 16)
(make-aes-ctr-entry 'aes192-ctr 24) (make-cipher-entry 'aes192-ctr '(aes ctr) 24)
(make-aes-ctr-entry 'aes256-ctr 32) (make-cipher-entry 'aes256-ctr '(aes ctr) 32)
(make-evp-cipher-entry 'aes128-cbc cipher:aes-128-cbc) (make-cipher-entry 'aes128-cbc '(aes cbc) 16)
(make-evp-cipher-entry 'aes192-cbc cipher:aes-192-cbc) (make-cipher-entry 'aes192-cbc '(aes cbc) 24)
(make-evp-cipher-entry 'aes256-cbc cipher:aes-256-cbc) (make-cipher-entry 'aes256-cbc '(aes cbc) 32)
(make-evp-cipher-entry '3des-cbc cipher:des-ede3) (make-cipher-entry '3des-cbc '(des-ede3 cbc) 24)
)) ;; TODO: actually test these! )) ;; TODO: actually test these!
(define (make-hmac-entry name digest key-length-or-false) (define (make-hmac-entry name digest-spec key-length-or-false)
(let* ((digest-length (digest-size digest)) (let* ((digest-length (digest-size digest-spec))
(key-length (or key-length-or-false digest-length))) (key-length (or key-length-or-false digest-length)))
(list name (list name
(supported-hmac name (supported-hmac name
(lambda (key) (lambda (key)
(lambda (blob) (lambda (blob)
(hmac digest key blob))) (hmac digest-spec key blob)))
digest-length digest-length
key-length)))) key-length))))
(define supported-hmac-algorithms (define supported-hmac-algorithms
(list (make-hmac-entry 'hmac-md5 digest:md5 #f) (list (make-hmac-entry 'hmac-md5 'md5 #f)
(make-hmac-entry 'hmac-sha1 digest:sha1 #f))) (make-hmac-entry 'hmac-sha1 'sha1 #f)))
(define supported-compression-algorithms '(none)) ;; TODO: zlib, and zlib delayed (define supported-compression-algorithms '(none)) ;; TODO: zlib, and zlib delayed
@ -161,7 +140,7 @@
(mac-names (map car supported-hmac-algorithms))) (mac-names (map car supported-hmac-algorithms)))
(make-parameter (make-parameter
(lambda () (lambda ()
(ssh-msg-kexinit (random-bytes 16) (ssh-msg-kexinit (crypto-random-bytes 16)
'(diffie-hellman-group14-sha1 '(diffie-hellman-group14-sha1
diffie-hellman-group1-sha1) diffie-hellman-group1-sha1)
'(ssh-dss) ;; TODO: offer ssh-rsa. This will '(ssh-dss) ;; TODO: offer ssh-rsa. This will
@ -190,7 +169,7 @@
null-hmac null-hmac
null-hmac-description)) null-hmac-description))
(define (apply-negotiated-options nk is-outbound?) (define (apply-negotiated-options conn-ds nk is-outbound?)
(match-define (new-keys is-server? (match-define (new-keys is-server?
derive-key derive-key
c2s-enc s2c-enc c2s-enc s2c-enc
@ -208,7 +187,8 @@
(define cipher-description (define cipher-description
(cond (cond
((assq enc supported-crypto-algorithms) => cadr) ((assq enc supported-crypto-algorithms) => cadr)
(else (disconnect-with-error SSH_DISCONNECT_KEY_EXCHANGE_FAILED (else (disconnect-with-error conn-ds
SSH_DISCONNECT_KEY_EXCHANGE_FAILED
"Could not find driver for encryption algorithm ~v" "Could not find driver for encryption algorithm ~v"
enc)))) enc))))
(define cipher (define cipher
@ -220,7 +200,8 @@
(define hmac-description (define hmac-description
(cond (cond
((assq mac supported-hmac-algorithms) => cadr) ((assq mac supported-hmac-algorithms) => cadr)
(else (disconnect-with-error SSH_DISCONNECT_KEY_EXCHANGE_FAILED (else (disconnect-with-error conn-ds
SSH_DISCONNECT_KEY_EXCHANGE_FAILED
"Could not find driver for HMAC algorithm ~v" "Could not find driver for HMAC algorithm ~v"
mac)))) mac))))
(define hmac (define hmac
@ -240,7 +221,7 @@
(mac (bit-string->bytes (bit-string-append (integer->bit-string sequence-number 32 #t) (mac (bit-string->bytes (bit-string-append (integer->bit-string sequence-number 32 #t)
packet)))) packet))))
(define (check-packet-length! actual-length limit block-size) (define (check-packet-length! conn-ds actual-length limit block-size)
(when (> actual-length limit) (when (> actual-length limit)
(log-warning (format "Packet of length ~v exceeded our limit of ~v" (log-warning (format "Packet of length ~v exceeded our limit of ~v"
actual-length actual-length
@ -250,7 +231,8 @@
;; exceed the packet size limit! (For example, sending a packet of ;; exceed the packet size limit! (For example, sending a packet of
;; length 65564 when I'm expecting a max of 65536.) So we actually ;; length 65564 when I'm expecting a max of 65536.) So we actually
;; enforce twice our actual limit. ;; enforce twice our actual limit.
(disconnect-with-error 0 ;; TODO: better reason code? (disconnect-with-error conn-ds
0 ;; TODO: better reason code?
"Packet of length ~v is longer than packet limit ~v" "Packet of length ~v is longer than packet limit ~v"
actual-length actual-length
limit)) limit))
@ -259,7 +241,8 @@
;; the length-of-length, but the requirements for transmitted ;; the length-of-length, but the requirements for transmitted
;; chunks of data are that they be block-size multiples ;; chunks of data are that they be block-size multiples
;; *including* the length-of-length ;; *including* the length-of-length
(disconnect-with-error SSH_DISCONNECT_PROTOCOL_ERROR (disconnect-with-error conn-ds
SSH_DISCONNECT_PROTOCOL_ERROR
"Packet of length ~v is not a multiple of block size ~v" "Packet of length ~v is not a multiple of block size ~v"
actual-length actual-length
block-size))) block-size)))
@ -279,190 +262,125 @@
;; Encrypted Packet Input ;; Encrypted Packet Input
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(struct ssh-reader-state (mode config sequence-number remaining-credit) #:prefab) (define (ssh-reader conn-ds conn update-input-handler)
(define (ssh-reader new-conversation)
(match-define (tcp-channel remote-addr local-addr _) new-conversation)
(define packet-size-limit (default-packet-limit)) (define packet-size-limit (default-packet-limit))
(define sequence-number 0)
(define remaining-credit 0)
(define (issue-credit state) (define config initial-crypto-configuration)
(match-define (ssh-reader-state _ (crypto-configuration _ desc _ _) _ message-credit) state)
(when (positive? message-credit)
(at-meta-level
(send-feedback (tcp-channel remote-addr local-addr
(tcp-credit (supported-cipher-block-size desc)))))))
(transition (ssh-reader-state 'packet-header initial-crypto-configuration 0 0) (define (current-cipher) (crypto-configuration-cipher config))
(at-meta-level (define (block-size)
(name-endpoint 'socket-reader (supported-cipher-block-size (crypto-configuration-cipher-description config)))
(subscriber (tcp-channel remote-addr local-addr ?) (define (decrypt-chunk chunk) ((or (current-cipher) values) chunk))
(match-state (and state (define (subsequent-block-size) (if (current-cipher) (block-size) 1))
(ssh-reader-state mode (define (hmac) (crypto-configuration-hmac config))
(crypto-configuration cipher
cipher-description
hmac
hmac-description)
sequence-number
remaining-credit))
(on-message
[(tcp-channel _ _ (? eof-object?))
(transition state (quit))]
[(tcp-channel _ _ (? bytes? encrypted-packet))
(let ()
(define block-size (supported-cipher-block-size cipher-description))
(define first-block-size block-size)
(define subsequent-block-size (if cipher block-size 1))
(define decryptor (if cipher cipher values))
(define (check-hmac packet-length payload-length packet) (define (issue-credit)
(define computed-hmac-bytes (apply-hmac hmac sequence-number packet)) (when (positive? remaining-credit)
(define mac-byte-count (bytes-length computed-hmac-bytes)) (send-bytes-credit conn (block-size))))
(if (positive? mac-byte-count)
(transition (struct-copy ssh-reader-state state
[mode `(packet-hmac ,computed-hmac-bytes
,mac-byte-count
,packet-length
,payload-length
,packet)])
(at-meta-level
(send-feedback (tcp-channel remote-addr local-addr
(tcp-credit mac-byte-count)))))
(finish-packet 0 packet-length payload-length packet)))
(define (finish-packet mac-byte-count packet-length payload-length packet) (define (handle-packet-header encrypted-packet _mode)
(define bytes-read (+ packet-length mac-byte-count)) (define first-block (decrypt-chunk encrypted-packet))
(define payload (subbytes packet 5 (+ 5 payload-length))) (define packet-length (integer-bytes->integer first-block #f #t 0 4))
(define new-credit (- remaining-credit 1)) (check-packet-length! conn-ds packet-length packet-size-limit (subsequent-block-size))
(define new-state (struct-copy ssh-reader-state state (define padding-length (bytes-ref first-block 4))
[mode 'packet-header] (define payload-length (- packet-length padding-length 1))
[sequence-number (+ sequence-number 1)] (define amount-of-packet-in-first-block (- (bytes-length first-block) 4)) ;; not incl length
[remaining-credit new-credit])) (define remaining-to-read (- packet-length amount-of-packet-in-first-block))
(transition new-state (if (positive? remaining-to-read)
(issue-credit new-state) (begin
(send-message (send-bytes-credit conn remaining-to-read)
(inbound-packet sequence-number (update-input-handler
payload #:on-data (lambda (encrypted-packet _mode)
(ssh-message-decode payload) (check-hmac packet-length
bytes-read)))) payload-length
(bytes-append first-block (decrypt-chunk encrypted-packet))))))
(check-hmac packet-length payload-length first-block)))
(match mode (define (check-hmac packet-length payload-length packet)
['packet-header (define computed-hmac-bytes (apply-hmac (hmac) sequence-number packet))
(define decrypted-packet (decryptor encrypted-packet)) (define mac-byte-count (bytes-length computed-hmac-bytes))
(define first-block decrypted-packet) (if (positive? mac-byte-count)
(define packet-length (integer-bytes->integer first-block #f #t 0 4)) (begin
(check-packet-length! packet-length packet-size-limit subsequent-block-size) (send-bytes-credit conn mac-byte-count)
(define padding-length (bytes-ref first-block 4)) (update-input-handler
(define payload-length (- packet-length padding-length 1)) #:on-data (lambda (received-hmac-bytes _mode)
(define amount-of-packet-in-first-block (if (equal? computed-hmac-bytes received-hmac-bytes)
(- (bytes-length first-block) 4)) ;; not incl length (finish-packet mac-byte-count packet-length payload-length packet)
(define remaining-to-read (- packet-length amount-of-packet-in-first-block)) (disconnect-with-error/local-info conn-ds
`((expected-hmac ,computed-hmac-bytes)
(actual-hmac ,received-hmac-bytes))
SSH_DISCONNECT_MAC_ERROR
"Corrupt MAC")))))
(finish-packet 0 packet-length payload-length packet)))
(if (positive? remaining-to-read) (define (finish-packet mac-byte-count packet-length payload-length packet)
(transition (struct-copy ssh-reader-state state (define bytes-read (+ packet-length mac-byte-count))
[mode `(packet-body ,packet-length (define payload (subbytes packet 5 (+ 5 payload-length)))
,payload-length (update-input-handler #:on-data handle-packet-header)
,first-block)]) (send! conn-ds (inbound-packet sequence-number
(at-meta-level payload
(send-feedback (tcp-channel remote-addr local-addr (ssh-message-decode payload)
(tcp-credit remaining-to-read))))) bytes-read))
(check-hmac packet-length payload-length first-block))] (set! sequence-number (+ sequence-number 1))
(set! remaining-credit (- remaining-credit 1))
(issue-credit))
[`(packet-body ,packet-length ,payload-length ,first-block) (update-input-handler
(define decrypted-packet (decryptor encrypted-packet)) #:on-eof (lambda () (stop-current-facet))
(check-hmac packet-length payload-length (bytes-append first-block #:on-data handle-packet-header)
decrypted-packet))]
[`(packet-hmac ,computed-hmac-bytes (at conn-ds
,mac-byte-count (when (message (inbound-credit $amount))
,packet-length (set! remaining-credit (+ remaining-credit amount))
,payload-length (issue-credit))
,main-packet)
(define received-hmac-bytes encrypted-packet) ;; not really encrypted! (when (message ($ nk (new-keys _ _ _ _ _ _ _ _)))
(if (equal? computed-hmac-bytes received-hmac-bytes) (set! config (apply-negotiated-options conn-ds nk #f)))))
(finish-packet mac-byte-count packet-length payload-length main-packet)
(disconnect-with-error/local-info `((expected-hmac ,computed-hmac-bytes)
(actual-hmac ,received-hmac-bytes))
SSH_DISCONNECT_MAC_ERROR
"Corrupt MAC"))]))])))))
(subscriber (inbound-credit (wild))
(match-state state
(on-message
[(inbound-credit amount)
(let ()
(define new-state (struct-copy ssh-reader-state state
[remaining-credit
(+ amount (ssh-reader-state-remaining-credit state))]))
(transition new-state
(issue-credit new-state)))])))
(subscriber (new-keys (wild)
(wild)
(wild) (wild)
(wild) (wild)
(wild) (wild))
(match-state state
(on-message
[(? new-keys? nk)
(transition (struct-copy ssh-reader-state state
[config (apply-negotiated-options nk #f)]))])))
(publisher (inbound-packet (wild) (wild) (wild) (wild)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Encrypted Packet Output ;; Encrypted Packet Output
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(struct ssh-writer-state (config sequence-number) #:prefab) ;; (struct ssh-writer-state (config sequence-number) #:prefab)
(define (ssh-writer new-conversation) (define (ssh-writer conn-ds conn)
(match-define (tcp-channel remote-addr local-addr _) new-conversation) (define config initial-crypto-configuration)
(transition (ssh-writer-state initial-crypto-configuration 0) (define sequence-number 0)
(publisher (outbound-byte-credit (wild)))
(subscriber (outbound-packet (wild)) (define (block-size)
(match-state (and state (supported-cipher-block-size (crypto-configuration-cipher-description config)))
(ssh-writer-state (crypto-configuration cipher (define (encrypt-chunk chunk) ((or (crypto-configuration-cipher config) values) chunk))
cipher-description (define (hmac) (crypto-configuration-hmac config))
hmac
hmac-description) (at conn-ds
sequence-number)) (when (message (outbound-packet $message))
(on-message (define pad-block-size (block-size))
[(outbound-packet message) (define payload (ssh-message-encode message))
(let () ;; There must be at least 4 bytes of padding, and padding needs to
(define pad-block-size (supported-cipher-block-size cipher-description)) ;; make the packet length a multiple of pad-block-size.
(define encryptor (if cipher cipher values)) (define unpadded-length (+ 4 ;; length of length
(define payload (ssh-message-encode message)) 1 ;; length of length-of-padding indicator
;; There must be at least 4 bytes of padding, and padding needs to (bit-string-byte-count payload)))
;; make the packet length a multiple of pad-block-size. (define min-padded-length (+ unpadded-length 4))
(define unpadded-length (+ 4 ;; length of length (define padded-length (round-up min-padded-length pad-block-size))
1 ;; length of length-of-padding indicator (define padding-length (- padded-length unpadded-length))
(bit-string-byte-count payload))) (define packet-length (- padded-length 4))
(define min-padded-length (+ unpadded-length 4)) ;; ^^ the packet length does *not* include itself!
(define padded-length (round-up min-padded-length pad-block-size)) (define packet (bit-string->bytes
(define padding-length (- padded-length unpadded-length)) (bit-string (packet-length :: integer bits 32)
(define packet-length (- padded-length 4)) (padding-length :: integer bits 8)
;; ^^ the packet length does *not* include itself! (payload :: binary)
(define packet (bit-string->bytes ((crypto-random-bytes padding-length) :: binary))))
(bit-string (packet-length :: integer bits 32) (define encrypted-packet (encrypt-chunk packet))
(padding-length :: integer bits 8) (define computed-hmac-bytes (apply-hmac (hmac) sequence-number packet))
(payload :: binary) (define mac-byte-count (bytes-length computed-hmac-bytes))
((random-bytes padding-length) :: binary)))) (send-data conn encrypted-packet)
(define encrypted-packet (encryptor packet)) (send-data conn computed-hmac-bytes)
(define computed-hmac-bytes (apply-hmac hmac sequence-number packet)) (send! conn-ds (outbound-byte-credit (+ (bytes-length encrypted-packet)
(define mac-byte-count (bytes-length computed-hmac-bytes)) (bytes-length computed-hmac-bytes))))
(transition (struct-copy ssh-writer-state state (set! sequence-number (+ sequence-number 1)))
[sequence-number (+ sequence-number 1)])
(at-meta-level (when (message ($ nk (new-keys _ _ _ _ _ _ _ _)))
(send-message (tcp-channel local-addr remote-addr encrypted-packet))) (set! config (apply-negotiated-options conn-ds nk #t)))))
(when (positive? mac-byte-count)
(at-meta-level
(send-message (tcp-channel local-addr remote-addr computed-hmac-bytes))))
(send-message
(outbound-byte-credit (+ (bytes-length encrypted-packet) mac-byte-count)))))])))
(subscriber (new-keys (wild)
(wild)
(wild) (wild)
(wild) (wild)
(wild) (wild))
(match-state state
(on-message
[(? new-keys? nk)
(transition
(struct-copy ssh-writer-state state [config (apply-negotiated-options nk #t)]))])))))

View File

@ -0,0 +1,12 @@
-----BEGIN DSA PRIVATE KEY-----
MIIBuwIBAAKBgQCEQ1YvOR7/MQByCPJt/FSO7NN7YO1VLqy7A95M07q6AaG5FZ2A
m9s8KZPlNFPrNhG8pRxxHhWgfBczoIObZi2saXeXQyTCUtHUejQBk+Xl31I+0SYU
/m5fIP3Q9UY3cR8LucsIQkJIcuLVpoMmtFA/EtxYs+roxm+wtMlgk/8HkQIVAObN
DEIjvgKwW9MKzRz8VXms/aDDAoGAeMnKQxj/iBSfQ3Wsd4ipCi3PdoLJ0+TJuiFG
0tmbxLxwC0YCR24YMeobva/SpSu6y48+2rjv9Wc9ZKwISbrdO6xrNgDJtoCZLGK+
C2DHEC3rBYFicOgpoysk/HsS/to3GtMnPyA2NJDR/cjUdgWBRg+4eAx1ZsVPjaJT
A5Z60tECgYAkhzk5oi/b3zxPEPoFYki2apR4mciJso/1mYvb6fpd+rzlihNrkFAA
LL+6uOofkyf32FIQhEN+JXDNMfaHreJkLPxGXIJ4FyUbrrZcxbmgJdh9NHd0L/mI
yIHlo+SImp1DLCEtRP1GwKv8Lm0/rFNpY/z5Os3qeXKw1swDvEMfywIVANtH4mhn
F6JfX/4/cJ4cpGlcgrWe
-----END DSA PRIVATE KEY-----

View File

@ -1,138 +0,0 @@
#lang racket/base
;;; SPDX-License-Identifier: LGPL-3.0-or-later
;;; SPDX-FileCopyrightText: Copyright © 2011-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
(require "../aes-ctr.rkt")
(require rackunit)
(require bitsyntax)
(check-equal? (let ((x (start-aes-ctr (make-bytes 16 97) (make-bytes 16 0))))
(aes-ctr-process! x #"abcdefghijklmnop"))
#"\275XO-\317^<d\16(\262\257Fv}e" ;; = bd584f2dcf5e3c640e28b2af46767d65
)
(check-equal? (let ((x (start-aes-ctr (make-bytes 16 97) (make-bytes 16 0))))
(let* ((b1 (aes-ctr-process! x #"abcdef"))
(b2 (aes-ctr-process! x #"ghijklmnop")))
(list b1 b2)))
(list #"\275XO-\317^"
#"<d\16(\262\257Fv}e"))
(check-equal? (let ((x (start-aes-ctr (make-bytes 16 97) (make-bytes 16 0))))
(aes-ctr-process! x #"\275XO-\317^<d\16(\262\257Fv}e"))
#"abcdefghijklmnop")
(check-equal? (let ((x (start-aes-ctr (make-bytes 16 97) (make-bytes 16 0))))
(let* ((b1 (aes-ctr-process! x #"\275XO-\317^"))
(b2 (aes-ctr-process! x #"<d\16(\262\257Fv}e")))
(list b1 b2)))
(list #"abcdef"
#"ghijklmnop"))
(check-equal? (let ((x (start-aes-ctr (make-bytes 16 97) (make-bytes 16 0))))
(aes-ctr-process! x #"abcdefghijklmnopabcdefghijklmnop"))
#"\275XO-\317^<d\16(\262\257Fv}e!v\301\23\6@\330\305\314c\27\374\276\330 \342")
(check-equal? (let ((x (start-aes-ctr (make-bytes 16 97) (make-bytes 16 0))))
(aes-ctr-process! x
#"\275XO-\317^<d\16(\262\257Fv}e!v\301\23\6@\330\305\314c\27\374\276\330 \342"))
#"abcdefghijklmnopabcdefghijklmnop")
;; Test vectors from http://tools.ietf.org/html/draft-ietf-ipsec-ciph-aes-ctr-05
(define (hex-string->bytes str) ;; grumble
(define cleaned (regexp-replace* #rx"[^0-9a-fA-F]+" str ""))
(define bits (* (string-length cleaned) 4))
(define n (string->number cleaned 16))
(integer->bit-string n bits #t))
(define (test-enc description key ivec plaintext ciphertext)
(let ((state (start-aes-ctr (hex-string->bytes key)
(hex-string->bytes ivec))))
(check-equal? (aes-ctr-process! state (hex-string->bytes plaintext))
(hex-string->bytes ciphertext)
(format "test-enc ~v" description))))
;; Test Vector #1: Encrypting 16 octets using AES-CTR with 128-bit key
(test-enc 1
"AE 68 52 F8 12 10 67 CC 4B F7 A5 76 55 77 F3 9E"
"00 00 00 30 00 00 00 00 00 00 00 00 00 00 00 01"
"53 69 6E 67 6C 65 20 62 6C 6F 63 6B 20 6D 73 67"
"E4 09 5D 4F B7 A7 B3 79 2D 61 75 A3 26 13 11 B8")
;; Test Vector #2: Encrypting 32 octets using AES-CTR with 128-bit key
(test-enc 2
"7E 24 06 78 17 FA E0 D7 43 D6 CE 1F 32 53 91 63"
"00 6C B6 DB C0 54 3B 59 DA 48 D9 0B 00 00 00 01"
(string-append "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"
"10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F")
(string-append "51 04 A1 06 16 8A 72 D9 79 0D 41 EE 8E DA D3 88"
"EB 2E 1E FC 46 DA 57 C8 FC E6 30 DF 91 41 BE 28"))
;; Test Vector #3: Encrypting 36 octets using AES-CTR with 128-bit key
(test-enc 3
"76 91 BE 03 5E 50 20 A8 AC 6E 61 85 29 F9 A0 DC"
"00 E0 01 7B 27 77 7F 3F 4A 17 86 F0 00 00 00 01"
(string-append "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"
"10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F"
"20 21 22 23")
(string-append "C1 CF 48 A8 9F 2F FD D9 CF 46 52 E9 EF DB 72 D7"
"45 40 A4 2B DE 6D 78 36 D5 9A 5C EA AE F3 10 53"
"25 B2 07 2F"))
;; Test Vector #4: Encrypting 16 octets using AES-CTR with 192-bit key
(test-enc 4
"16 AF 5B 14 5F C9 F5 79 C1 75 F9 3E 3B FB 0E ED 86 3D 06 CC FD B7 85 15"
"00 00 00 48 36 73 3C 14 7D 6D 93 CB 00 00 00 01"
"53 69 6E 67 6C 65 20 62 6C 6F 63 6B 20 6D 73 67"
"4B 55 38 4F E2 59 C9 C8 4E 79 35 A0 03 CB E9 28")
;; Test Vector #5: Encrypting 32 octets using AES-CTR with 192-bit key
(test-enc 5
"7C 5C B2 40 1B 3D C3 3C 19 E7 34 08 19 E0 F6 9C 67 8C 3D B8 E6 F6 A9 1A"
"00 96 B0 3B 02 0C 6E AD C2 CB 50 0D 00 00 00 01"
(string-append "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"
"10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F")
(string-append "45 32 43 FC 60 9B 23 32 7E DF AA FA 71 31 CD 9F"
"84 90 70 1C 5A D4 A7 9C FC 1F E0 FF 42 F4 FB 00"))
;; Test Vector #6: Encrypting 36 octets using AES-CTR with 192-bit key
(test-enc 6
"02 BF 39 1E E8 EC B1 59 B9 59 61 7B 09 65 27 9B F5 9B 60 A7 86 D3 E0 FE"
"00 07 BD FD 5C BD 60 27 8D CC 09 12 00 00 00 01"
(string-append "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"
"10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F"
"20 21 22 23")
(string-append "96 89 3F C5 5E 5C 72 2F 54 0B 7D D1 DD F7 E7 58"
"D2 88 BC 95 C6 91 65 88 45 36 C8 11 66 2F 21 88"
"AB EE 09 35"))
;; Test Vector #7: Encrypting 16 octets using AES-CTR with 256-bit key
(test-enc 7
(string-append "77 6B EF F2 85 1D B0 6F 4C 8A 05 42 C8 69 6F 6C"
"6A 81 AF 1E EC 96 B4 D3 7F C1 D6 89 E6 C1 C1 04")
"00 00 00 60 DB 56 72 C9 7A A8 F0 B2 00 00 00 01"
"53 69 6E 67 6C 65 20 62 6C 6F 63 6B 20 6D 73 67"
"14 5A D0 1D BF 82 4E C7 56 08 63 DC 71 E3 E0 C0")
;; Test Vector #8: Encrypting 32 octets using AES-CTR with 256-bit key
(test-enc 8
(string-append "F6 D6 6D 6B D5 2D 59 BB 07 96 36 58 79 EF F8 86"
"C6 6D D5 1A 5B 6A 99 74 4B 50 59 0C 87 A2 38 84")
"00 FA AC 24 C1 58 5E F1 5A 43 D8 75 00 00 00 01"
(string-append "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"
"10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F")
(string-append "F0 5E 23 1B 38 94 61 2C 49 EE 00 0B 80 4E B2 A9"
"B8 30 6B 50 8F 83 9D 6A 55 30 83 1D 93 44 AF 1C"))
;; Test Vector #9: Encrypting 36 octets using AES-CTR with 256-bit key
(test-enc 9
(string-append "FF 7A 61 7C E6 91 48 E4 F1 72 6E 2F 43 58 1D E2"
"AA 62 D9 F8 05 53 2E DF F1 EE D6 87 FB 54 15 3D")
"00 1C C5 B7 51 A5 1D 70 A1 C1 11 48 00 00 00 01"
(string-append "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"
"10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F"
"20 21 22 23")
(string-append "EB 6C 52 82 1D 0B BB F7 CE 75 94 46 2A CA 4F AA"
"B4 07 DF 86 65 69 FD 07 F4 8C C0 B5 83 D6 07 1F"
"1E C0 E6 B8"))