(Re)keying. Oakley DH groups. AES CTR mode.

This commit is contained in:
Tony Garnock-Jones 2011-08-16 02:46:45 -04:00
parent 6f7e6ea573
commit 6c860ed727
8 changed files with 1026 additions and 101 deletions

64
aes-ctr.rkt Normal file
View File

@ -0,0 +1,64 @@
#lang racket/base
;; 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)

7
oakley-group-14.pem Normal file
View File

@ -0,0 +1,7 @@
-----BEGIN DH PARAMETERS-----
MIIBCAKCAQEA///////////JD9qiIWjCNMTGYouA3BzRKQJOCIpnzHQCC76mOxObIlFKCHmO
NATd75UZs806QxswKwpt8l8UN0/hNW1tUcJF5IW1dmJefsb0TELppjftawv/XLb0Brft7jhr
+1qJn6WunyQRfEsf5kkoZlHs5Fs9wgB8uKFjvwWY2kg2HFXTmmkWP6j9JM9fg2VdI9yjrZYc
YvNWIIVSu57VKQdwlpZtZww1Tkq8mATxdGwIyhghfDKQXkYuNs474553LBgOhgObJ4Oi7Aei
j7XFXfBvTFLJ3ivL9pVYFxg5lUl86pVq5RXSJhiY+gUQFXKOWoqsqmj//////////wIBAg==
-----END DH PARAMETERS-----

5
oakley-group-2.pem Normal file
View File

@ -0,0 +1,5 @@
-----BEGIN DH PARAMETERS-----
MIGHAoGBAP//////////yQ/aoiFowjTExmKLgNwc0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE
3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHCReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/ta
iZ+lrp8kEXxLH+ZJKGZR7OZTgf//////////AgEC
-----END DH PARAMETERS-----

28
oakley-groups.rkt Normal file
View File

@ -0,0 +1,28 @@
#lang racket/base
;; Construct Oakley MODP Diffie-Hellman groups from RFCs 2409 and 3526.
(provide dh:oakley-group-2
dh:oakley-group-14)
;;(require (planet vyzo/crypto))
(require (planet vyzo/crypto/dh))
(require (only-in net/base64 base64-decode))
(define dh:oakley-group-2
(make-!dh
1024
(base64-decode
#"MIGHAoGBAP//////////yQ/aoiFowjTExmKLgNwc0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE
3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHCReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/ta
iZ+lrp8kEXxLH+ZJKGZR7OZTgf//////////AgEC")))
(define dh:oakley-group-14
(make-!dh
2048
(base64-decode
#"MIIBCAKCAQEA///////////JD9qiIWjCNMTGYouA3BzRKQJOCIpnzHQCC76mOxObIlFKCHmO
NATd75UZs806QxswKwpt8l8UN0/hNW1tUcJF5IW1dmJefsb0TELppjftawv/XLb0Brft7jhr
+1qJn6WunyQRfEsf5kkoZlHs5Fs9wgB8uKFjvwWY2kg2HFXTmmkWP6j9JM9fg2VdI9yjrZYc
YvNWIIVSu57VKQdwlpZtZww1Tkq8mATxdGwIyhghfDKQXkYuNs474553LBgOhgObJ4Oi7Aei
j7XFXfBvTFLJ3ivL9pVYFxg5lUl86pVq5RXSJhiY+gUQFXKOWoqsqmj//////////wIBAg==")))

View File

@ -14,7 +14,19 @@
(provide ssh-message-decode
ssh-message-encode)
(provide (struct-out ssh-msg-kexinit))
(provide t:boolean
t:string
t:mpint
mpint-width
t:name-list)
(provide (struct-out ssh-msg-kexinit)
(struct-out ssh-msg-kexdh-init)
(struct-out ssh-msg-kexdh-reply)
(struct-out ssh-msg-disconnect)
(struct-out ssh-msg-unimplemented)
(struct-out ssh-msg-newkeys)
(struct-out ssh-msg-ignore))
(define decoder-map (make-hasheqv))
@ -22,13 +34,11 @@
(make-struct-type-property 'ssh-message-encoder))
(define (ssh-message-decode packet)
(let ((type-code (bytes-ref packet 0)))
((hash-ref decoder-map
type-code
(lambda () (error 'ssh-message-decode
"Unknown message packet type number ~v"
type-code)))
packet)))
(define type-code (bytes-ref packet 0))
(define decoder (hash-ref decoder-map type-code #f))
(if decoder
(decoder packet)
#f))
(define (ssh-message-encode m)
(bit-string->bytes ((ssh-message-encoder m) m)))
@ -187,3 +197,24 @@
(name-list languages_server_to_client)
(boolean first_kex_packet_follows)
(uint32 reserved))
(define-ssh-message-type ssh-msg-kexdh-init SSH_MSG_KEXDH_INIT
(mpint e))
(define-ssh-message-type ssh-msg-kexdh-reply SSH_MSG_KEXDH_REPLY
(string host-key)
(mpint f)
(string h-signature))
(define-ssh-message-type ssh-msg-disconnect SSH_MSG_DISCONNECT
(uint32 reason-code)
(string description)
(string language-tag))
(define-ssh-message-type ssh-msg-unimplemented SSH_MSG_UNIMPLEMENTED
(uint32 sequence-number))
(define-ssh-message-type ssh-msg-newkeys SSH_MSG_NEWKEYS)
(define-ssh-message-type ssh-msg-ignore SSH_MSG_IGNORE
(string data))

View File

@ -2,6 +2,8 @@
(require "mapping.rkt")
(provide (all-defined-out)) ;; I know, I know
;; Assigned numbers, from RFCs 4250 and 4344.
;; Protocol packets have message numbers in the range 1 to 255. These
@ -34,7 +36,21 @@
;;
;; 192 to 255 Local extensions
(provide (all-defined-out)) ;; I know, I know
(define (ssh-msg-type-transport-layer? msg-type) (>= 49 msg-type 1))
(define (ssh-msg-type-transport-layer-generic? msg-type) (>= 19 msg-type 1))
(define (ssh-msg-type-transport-layer-algorithm-negotiation? msg-type) (>= 29 msg-type 20))
(define (ssh-msg-type-transport-layer-key-exchange? msg-type) (>= 49 msg-type 30))
(define (ssh-msg-type-authentication? msg-type) (>= 79 msg-type 50))
(define (ssh-msg-type-authentication-generic? msg-type) (>= 59 msg-type 50))
(define (ssh-msg-type-authentication-specific? msg-type) (>= 79 msg-type 60))
(define (ssh-msg-type-connection? msg-type) (>= 127 msg-type 80))
(define (ssh-msg-type-connection-generic? msg-type) (>= 89 msg-type 80))
(define (ssh-msg-type-connection-channel? msg-type) (>= 127 msg-type 90))
(define (ssh-msg-type-client? msg-type) (>= 191 msg-type 128))
(define (ssh-msg-type-local? msg-type) (>= 255 msg-type 192))
;;; SSH message type IDs.
;;
@ -48,6 +64,8 @@
(define SSH_MSG_SERVICE_ACCEPT 6) ;[SSH-TRANS]
(define SSH_MSG_KEXINIT 20) ;[SSH-TRANS]
(define SSH_MSG_NEWKEYS 21) ;[SSH-TRANS]
(define SSH_MSG_KEXDH_INIT 30) ;RFC 4253 errata
(define SSH_MSG_KEXDH_REPLY 31) ;RFC 4253 errata
(define SSH_MSG_USERAUTH_REQUEST 50) ;[SSH-USERAUTH]
(define SSH_MSG_USERAUTH_FAILURE 51) ;[SSH-USERAUTH]
(define SSH_MSG_USERAUTH_SUCCESS 52) ;[SSH-USERAUTH]

View File

@ -2,33 +2,92 @@
(require (planet tonyg/bitsyntax))
(require (planet vyzo/crypto:2:3))
(require (planet vyzo/crypto/util)) ;; hex, unhex
(require racket/port)
(require racket/bool)
(require rackunit)
(require "aes-ctr.rkt")
(require "safe-io.rkt")
(require "ssh-numbers.rkt")
(require "ssh-message-types.rkt")
(require "oakley-groups.rkt")
;; An exn:fail:contract:protocol, when thrown by the transport (TODO:
;; clarify scope of this behaviour) will cause a SSH_MSG_DISCONNECT to
;; be sent to the remote party with the included reason code, using
;; the exn-message as the description. The local-info field is useful
;; information for diagnosing problems known to the local stack that
;; should not be transmitted to the remote party. For example, upon
;; 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
;; wire because we could be experiencing some kind of attack.
(struct exn:fail:contract:protocol exn:fail:contract
(reason-code local-info originated-at-peer?)
#:transparent)
;; A RekeyState is one of
;; - a (rekey-wait Number Number), representing a time or
;; transfer-amount by which rekeying should be started
;; - a (rekey-local SshMsgKexinit), when we've sent our local
;; algorithm list and are waiting for the other party to send theirs
;; - a (rekey-in-progress KeyExchangeState), when both our local
;; algorithm list has been sent and the remote one has arrived and the
;; actual key exchange has begun
(struct rekey-wait (deadline threshold-bytes) #:transparent)
(struct rekey-local (local-algorithms) #:transparent)
(struct rekey-in-progress (state) #:transparent)
(struct stream-state (port
cipher
cipher-description
sequence-number
bytes-transferred
hmac
hmac-description
packet-size-limit)
#:transparent)
;; Generic inputs into the exchange-hash part of key
;; exchange. Diffie-Hellman uses these fields along with the host key,
;; the exchange values, and the shared secret to get the final hash.
(struct exchange-hash-info (client-id
server-id
client-kexinit-bytes
server-kexinit-bytes)
#:transparent)
;; Description of a supported cipher.
(struct supported-cipher (name factory key-length block-size iv-length)
#:transparent)
;; Description of a supported hmac algorithm.
(struct supported-hmac (name factory digest-length key-length)
#:transparent)
(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 required-peer-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))
(let ((my-id (client-identification-string)))
(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 my-id out)
(display "\r\n" out)
(flush-output out)
my-id))
(define (read-preamble-and-identification! in)
(let ((line (read-line-limited in 253))) ;; 255 incl CRLF
@ -38,102 +97,679 @@
line
(read-preamble-and-identification! in))))
(define (disconnect-with-error reason-code format-string . args)
(apply disconnect-with-error/local-info '() reason-code format-string args))
(define (disconnect-with-error/local-info local-info reason-code format-string . args)
(let ((message (apply format format-string args)))
(raise (exn:fail:contract:protocol message
(current-continuation-marks)
reason-code
local-info
#f))))
(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)))
(disconnect-with-error 0 ;; TODO: better reason code?
"Packet of length ~v is longer than packet limit ~v"
actual-length
limit))
(when (not (zero? (modulo (+ actual-length 4) block-size)))
;; the +4 is because the length sent on the wire doesn't include
;; the length-of-length, but the requirements for transmitted
;; chunks of data are that they be block-size multiples
;; *including* the length-of-length
(disconnect-with-error SSH_DISCONNECT_PROTOCOL_ERROR
"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))))
(define (read-bytes/timeout count in timeout)
(sync/timeout timeout (read-bytes-evt count in)))
(define (read-bytes/decrypt count in timeout decryptor)
(let ((encrypted (read-bytes/timeout count in timeout)))
(cond
((false? encrypted) #f)
((eof-object? encrypted) eof)
(else (decryptor encrypted)))))
(define default-packet-limit (make-parameter 65536))
(define rekey-interval (make-parameter 5)) ;;3600))
(define rekey-volume (make-parameter 1000000000))
(define inter-packet-timeout (make-parameter 1)) ;;300))
(define intra-packet-timeout (make-parameter 1)) ;;300))
(define (make-evp-cipher-entry name cipher)
(list name
(supported-cipher name
(lambda (enc? key iv)
(let ((state ((if enc? cipher-encrypt cipher-decrypt)
cipher key iv #:padding #f)))
(lambda (block)
(cipher-update! state block))))
(cipher-key-length cipher)
(cipher-block-size cipher)
(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
(list
(make-aes-ctr-entry 'aes128-ctr 16)
(make-aes-ctr-entry 'aes192-ctr 24)
(make-aes-ctr-entry 'aes256-ctr 32)
(make-evp-cipher-entry 'aes128-cbc cipher:aes-128-cbc)
(make-evp-cipher-entry 'aes192-cbc cipher:aes-192-cbc)
(make-evp-cipher-entry 'aes256-cbc cipher:aes-256-cbc)
(make-evp-cipher-entry '3des-cbc cipher:des-ede3)
)) ;; TODO: actually test these!
(define (make-hmac-entry name digest key-length-or-false)
(let* ((digest-length (digest-size digest))
(key-length (if (false? key-length-or-false)
digest-length
key-length-or-false)))
(list name
(supported-hmac name
(lambda (key)
(lambda (blob)
(hmac digest key blob)))
digest-length
key-length))))
(define supported-hmac-algorithms
(list (make-hmac-entry 'hmac-md5 digest:md5 #f)
(make-hmac-entry 'hmac-sha1 digest:sha1 #f)))
(define supported-compression-algorithms '(none)) ;; TODO: zlib, and zlib delayed
(define local-algorithm-list
(let ((crypto-names (map car supported-crypto-algorithms))
(mac-names (map car supported-hmac-algorithms)))
(make-parameter
(lambda ()
(ssh-msg-kexinit (random-bytes 16)
'(diffie-hellman-group14-sha1
diffie-hellman-group1-sha1)
'(ssh-rsa ssh-dss)
crypto-names
crypto-names
mac-names
mac-names
supported-compression-algorithms
supported-compression-algorithms
'()
'()
#f
0)))))
(define (bump-sequence-number! state byte-count)
(struct-copy stream-state state
[sequence-number
(+ 1 (stream-state-sequence-number state))]
[bytes-transferred
(+ byte-count (stream-state-bytes-transferred state))]))
;; Read and decode a transport message from in-state. If it can't be
;; decoded (we don't support that message type), complain with a
;; SSH_MSG_UNIMPLEMENTED packet. Finally, return a quadruple of the
;; packet, the decoded message, the updated input state, and the
;; updated output state. May return eof or #f for end-of-file or
;; timeout, respectively, depending on error-on-eof-or-timeout.
(define (read-message in-state out-state [error-on-eof-or-timeout #t])
(let-values (((packet in-state) (read-packet in-state error-on-eof-or-timeout)))
(if (not (bytes? packet))
(values packet packet in-state out-state)
(let ((message (ssh-message-decode packet)))
(write `(received ,message)) (newline) (flush-output)
(if message
(values packet message in-state out-state)
(let ((bad-seq-num (- (stream-state-sequence-number in-state) 1)))
;; TODO: remove this debug output
(display "BAD PACKET ")
(display (hex packet))
(newline)
(flush-output)
(read-message in-state
(write-message! (ssh-msg-unimplemented bad-seq-num)
out-state)
error-on-eof-or-timeout)))))))
;; 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 (read-packet in-state error-on-eof-or-timeout)
(define cipher (stream-state-cipher in-state))
(define block-size (supported-cipher-block-size (stream-state-cipher-description in-state)))
(define in (stream-state-port in-state))
(define first-block-size block-size)
(define subsequent-block-size (if cipher block-size 1))
(define decryptor (if cipher cipher values))
(define first-block (read-bytes/decrypt first-block-size in
(inter-packet-timeout) decryptor))
(cond
((false? first-block)
(if error-on-eof-or-timeout
(disconnect-with-error SSH_DISCONNECT_CONNECTION_LOST
"Timeout waiting for a packet")
(values #f in-state)))
((eof-object? first-block)
(if error-on-eof-or-timeout
(error 'read-packet "End-of-file at the start of a packet")
(values first-block in-state)))
(else
(let* ((packet-length (integer-bytes->integer first-block #f #t 0 4)))
(check-packet-length! packet-length
(stream-state-packet-size-limit in-state)
subsequent-block-size)
(let* ((padding-length (bytes-ref first-block 4))
(payload-length (- packet-length padding-length 1))
(amount-of-packet-in-first-block (- (bytes-length first-block) 4)) ;; not incl length
(remaining-to-read (- packet-length amount-of-packet-in-first-block)))
(define (read-packet-trailer packet)
(let ((bytes-read (+ (check-hmac! (apply-hmac (stream-state-hmac in-state)
(stream-state-sequence-number in-state)
packet)
in)
packet-length)))
(values (subbytes packet 5 (+ 5 payload-length))
(bump-sequence-number! in-state bytes-read))))
(if (positive? remaining-to-read)
(let ((trailing-blocks (read-bytes/decrypt remaining-to-read in
(intra-packet-timeout) decryptor)))
(cond
((false? trailing-blocks)
(disconnect-with-error SSH_DISCONNECT_CONNECTION_LOST
"Timeout partway through reading a packet"))
((eof-object? trailing-blocks)
(if error-on-eof-or-timeout
(error 'read-packet "End-of-file during a packet")
(values trailing-blocks in-state)))
(else
(read-packet-trailer (bytes-append first-block trailing-blocks)))))
(read-packet-trailer first-block)))))))
(define (null-mac-checker sequence-number packet in)
'ok)
(define (round-up what to)
(* to (quotient (+ what (- to 1)) to)))
(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))
(check-equal? (round-up 0 8) 0)
(check-equal? (round-up 1 8) 8)
(check-equal? (round-up 7 8) 8)
(check-equal? (round-up 8 8) 8)
(check-equal? (round-up 9 8) 16)
(define (write-packet! . args)
(error 'write-packet "Unimplemented"))
(define (write-message! message out-state [flush #f])
(write `(sending ,message)) (newline) (flush-output)
(write-packet! (ssh-message-encode message) out-state flush))
(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)
cipher
check-mac!))
(lambda args
(error 'rekey "Unimplemented")))))))
(define (write-packet! payload out-state flush)
(define cipher (stream-state-cipher out-state))
(define pad-block-size (supported-cipher-block-size (stream-state-cipher-description out-state)))
(define out (stream-state-port out-state))
(define encryptor (if cipher cipher values))
;; There must be at least 4 bytes of padding, and padding needs to
;; make the packet length a multiple of pad-block-size.
(define unpadded-length (+ 4 ;; length of length
1 ;; length of padding indicator
(bit-string-byte-count payload)))
(define min-padded-length (+ unpadded-length 4))
(define padded-length (round-up min-padded-length pad-block-size))
(define padding-length (- padded-length unpadded-length))
(define packet-length (- padded-length 4)) ;; the packet length does *not* include itself!
(define packet (bit-string->bytes
(bit-string (packet-length :: integer bits 32)
(padding-length :: integer bits 8)
(payload :: binary)
((random-bytes padding-length) :: binary))))
(define encrypted-packet (encryptor packet))
(define computed-hmac-bytes (apply-hmac (stream-state-hmac out-state)
(stream-state-sequence-number out-state)
packet))
(define mac-byte-count (bytes-length computed-hmac-bytes))
(write-bytes encrypted-packet out)
(when (positive? mac-byte-count)
(write-bytes computed-hmac-bytes out))
(when flush
(flush-output out))
(bump-sequence-number! out-state (+ (bytes-length encrypted-packet) mac-byte-count)))
(close-input-port in)
(close-output-port out)
result))
(define (apply-hmac mac sequence-number packet)
(mac (bit-string->bytes (bit-string-append (integer->bit-string sequence-number 32 #t) packet))))
(define (check-hmac! computed-hmac-bytes in)
(define mac-byte-count (bytes-length computed-hmac-bytes))
(when (positive? mac-byte-count)
(let ((received-hmac-bytes (read-bytes/timeout mac-byte-count in
(intra-packet-timeout))))
(cond
((false? received-hmac-bytes)
(disconnect-with-error SSH_DISCONNECT_CONNECTION_LOST
"Timeout reading MAC"))
((eof-object? received-hmac-bytes)
(disconnect-with-error SSH_DISCONNECT_CONNECTION_LOST
"EOF instead of MAC"))
(else
(when (not (equal? computed-hmac-bytes received-hmac-bytes))
(disconnect-with-error/local-info `((expected-hmac ,computed-hmac-bytes)
(actual-hmac ,received-hmac-bytes))
SSH_DISCONNECT_MAC_ERROR
"Corrupt MAC"))))))
mac-byte-count)
(define null-cipher-description
(supported-cipher 'none
(lambda (enc? key iv)
(error 'null-cipher-description
"Cannot construct null cipher instance"))
0
8 ;; pseudo-block-size for packet I/O
0))
(define (null-hmac blob)
#"")
(define null-hmac-description
(supported-hmac 'none
(lambda (key)
(error 'null-hmac-description
"Cannot construct null hmac instance"))
0
0))
(define (encoded-packet-msg-type encoded-packet)
(bytes-ref encoded-packet 0))
(define (key-exchange-init? encoded-packet)
(= (encoded-packet-msg-type encoded-packet) SSH_MSG_KEXINIT))
(define (acceptable-during-key-exchange? encoded-packet)
;; See end of RFC 4253 section 7.1.
(let ((msg-type (encoded-packet-msg-type encoded-packet)))
(and (ssh-msg-type-transport-layer? msg-type)
(not (memv msg-type (list SSH_MSG_SERVICE_REQUEST
SSH_MSG_SERVICE_ACCEPT
SSH_MSG_KEXINIT))))))
(define (default-stream-state port)
(stream-state port
#f ;; cipher
null-cipher-description
0
0
null-hmac
null-hmac-description
(default-packet-limit)))
(define (ssh-session role in out seed message-handler)
(define local-identification-string (send-preamble-and-identification! out))
(with-handlers
((exn:fail? (lambda (e)
(close-input-port in)
(close-output-port out)
(raise e))))
(let ((peer-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-peer-identification-regex)
peer-identification-string))
(display "Invalid identification\r\n" out)
(flush-output out)
(error 'ssh-session
"Invalid peer identification string ~v"
peer-identification-string))
(define result
(let ((orig-in-state (default-stream-state in))
(orig-out-state (default-stream-state out)))
(ssh-session-loop role
local-identification-string
peer-identification-string
message-handler
#f
seed
(rekey-in-seconds-or-bytes -1 -1 orig-in-state orig-out-state)
orig-in-state
orig-out-state)))
(close-input-port in)
(close-output-port out)
result)))
(define (rekey-in-seconds-or-bytes delta-seconds delta-bytes in-state out-state)
(rekey-wait (+ (current-seconds) delta-seconds)
(+ (stream-state-bytes-transferred in-state)
(stream-state-bytes-transferred out-state)
delta-bytes)))
(define (time-to-rekey? rekey in-state out-state)
(and (rekey-wait? rekey)
(or (>= (current-seconds) (rekey-wait-deadline rekey))
(>= (+ (stream-state-bytes-transferred in-state)
(stream-state-bytes-transferred out-state))
(rekey-wait-threshold-bytes rekey)))))
(define (maybe-send-disconnect-message! e out-state)
(if (exn:fail:contract:protocol-originated-at-peer? e)
out-state
(write-message! (ssh-msg-disconnect (exn:fail:contract:protocol-reason-code e)
(string->bytes/utf-8 (exn-message e))
#"")
out-state
#t)))
(define (write-messages! outbound-messages out-state)
(let ((final-state (foldl write-message! out-state outbound-messages)))
(flush-output (stream-state-port final-state))
final-state))
(define (ssh-session-loop role local-id remote-id message-handler
session-id
seed rekey in-state out-state)
(let loop ((seed seed)
(rekey rekey)
(in-state in-state)
(out-state out-state))
(with-handlers
((exn:fail:contract:protocol? (lambda (e)
(maybe-send-disconnect-message! e out-state)
(raise e))))
(if (time-to-rekey? rekey in-state out-state)
(let ((algs ((local-algorithm-list))))
(loop seed
(rekey-local algs)
in-state
(write-message! algs out-state #t)))
(let-values (((packet message in-state out-state)
(read-message in-state out-state #f)))
(cond
((eof-object? packet) seed)
((false? packet)
;; Timeout waiting for a message.
(loop seed rekey in-state out-state))
((key-exchange-init? packet)
(if (rekey-in-progress? rekey)
(disconnect-with-error SSH_DISCONNECT_PROTOCOL_ERROR
"Received SSH_MSG_KEXINIT during ongoing key exchange")
(let* ((algs (if (rekey-local? rekey)
(rekey-local-local-algorithms rekey)
((local-algorithm-list))))
(encoded-algs (ssh-message-encode algs))
(out-state (if (rekey-wait? rekey)
(write-packet! encoded-algs out-state #t)
out-state)))
(start-key-exchange session-id ;; may be #f, in which case will change below
role
local-id
encoded-algs
algs
remote-id
packet
message
in-state
out-state
(lambda (session-id in-state out-state)
(ssh-session-loop
role
local-id
remote-id
message-handler
session-id ;; just in case it changed
seed
(rekey-in-seconds-or-bytes (rekey-interval)
(rekey-volume)
in-state
out-state)
in-state
(write-message! (ssh-msg-ignore #"hello world")
out-state #t)))))))
((and (rekey-in-progress? rekey)
(not (acceptable-during-key-exchange? packet)))
(disconnect-with-error SSH_DISCONNECT_PROTOCOL_ERROR
"Unacceptable packet type ~v"
(encoded-packet-msg-type packet)))
(else
(message-handler seed
message
(lambda (outbound-messages new-seed)
(loop new-seed
rekey
in-state
(write-messages! outbound-messages out-state)))))))))))
(define (best-result getter client-algs server-algs)
(define client-list0 (getter client-algs))
(define server-list (getter server-algs))
(let loop ((client-list client-list0))
(cond
((null? client-list) (disconnect-with-error/local-info
`((client-list ,client-list0)
(server-list ,server-list))
SSH_DISCONNECT_KEY_EXCHANGE_FAILED
"Could not agree on a suitable algorithm for ~v"
getter))
((memq (car client-list) server-list) (car client-list))
(else (loop (cdr client-list))))))
(define (apply-negotiated-options state role outbound derive-key
c2s-enc s2c-enc
c2s-mac s2c-mac
c2s-zip s2c-zip)
;; TODO: zip
;; TODO: make this less ugly. Compute all the keys, select just the ones we need afterward?
(define c2s (case role
((client) outbound)
((server) (not outbound))))
(define enc (if c2s c2s-enc s2c-enc))
(define mac (if c2s c2s-mac s2c-mac))
(define zip (if c2s c2s-zip s2c-zip))
(struct-copy stream-state state
[cipher (cond
((assq enc supported-crypto-algorithms) =>
(lambda (entry)
(define c (cadr entry))
(define key (derive-key (if c2s #"C" #"D")
(supported-cipher-key-length c)))
(define iv (derive-key (if c2s #"A" #"B")
(supported-cipher-iv-length c)))
(define factory (supported-cipher-factory c))
;;(pretty-print `(,role ,(if c2s 'c2s 's2c) (key ,(hex key)) (iv ,(hex iv))))
(factory outbound key iv)))
(else (disconnect-with-error SSH_DISCONNECT_KEY_EXCHANGE_FAILED
"Could not find driver for encryption algorithm ~v"
enc)))]
[hmac (cond
((assq mac supported-hmac-algorithms) =>
(lambda (entry)
(define h (cadr entry))
(define factory (supported-hmac-factory h))
(define key (derive-key (if c2s #"E" #"F")
(supported-hmac-key-length h)))
;;(pretty-print `(,role ,(if c2s 'c2s 's2c) (key ,(hex key))))
(factory key)))
(else (disconnect-with-error SSH_DISCONNECT_KEY_EXCHANGE_FAILED
"Could not find driver for HMAC algorithm ~v"
mac)))]))
(define (start-key-exchange old-session-id ;; bytes or #f
role ;; 'client or 'server
local-id ;; string
encoded-local-algs ;; bytes, an encoded ssh-msg-kexinit
local-algs ;; ssh-msg-kexinit
remote-id ;; string
encoded-remote-algs ;; bytes, an encoded ssh-msg-kexinit
remote-algs ;; ssh-msg-kexinit
in-state
out-state
finish-key-exchange)
(when (not (memq role '(client server)))
(error 'start-key-exchange "Illegal role ~v, must be either 'client or 'server" role))
(define c (case role ((client) local-algs) ((server) remote-algs)))
(define s (case role ((client) remote-algs) ((server) local-algs)))
(define kex-alg (best-result ssh-msg-kexinit-kex_algorithms c s))
(define host-key-alg (best-result ssh-msg-kexinit-server_host_key_algorithms c s))
(define c2s-enc (best-result ssh-msg-kexinit-encryption_algorithms_client_to_server c s))
(define s2c-enc (best-result ssh-msg-kexinit-encryption_algorithms_server_to_client c s))
(define c2s-mac (best-result ssh-msg-kexinit-mac_algorithms_client_to_server c s))
(define s2c-mac (best-result ssh-msg-kexinit-mac_algorithms_server_to_client c s))
(define c2s-zip (best-result ssh-msg-kexinit-compression_algorithms_client_to_server c s))
(define s2c-zip (best-result ssh-msg-kexinit-compression_algorithms_server_to_client c s))
;; Ignore languages.
;; Don't check the reserved field here, either. TODO: should we?
(define (guess-matches? chosen-value getter)
(let ((remote-choices (getter remote-algs)))
(and (pair? remote-choices) ;; not strictly necessary because of
;; the error behaviour of
;; best-result.
(eq? (car remote-choices) ;; the remote peer's guess for this parameter
chosen-value))))
(define should-discard-first-kex-packet
(and (ssh-msg-kexinit-first_kex_packet_follows remote-algs)
;; They've already transmitted their guess. Does their guess match
;; what we've actually selected?
(not (and
(guess-matches? kex-alg ssh-msg-kexinit-kex_algorithms)
(guess-matches? host-key-alg ssh-msg-kexinit-server_host_key_algorithms)
(guess-matches? c2s-enc ssh-msg-kexinit-encryption_algorithms_client_to_server)
(guess-matches? s2c-enc ssh-msg-kexinit-encryption_algorithms_server_to_client)
(guess-matches? c2s-mac ssh-msg-kexinit-mac_algorithms_client_to_server)
(guess-matches? s2c-mac ssh-msg-kexinit-mac_algorithms_server_to_client)
(guess-matches? c2s-zip ssh-msg-kexinit-compression_algorithms_client_to_server)
(guess-matches? s2c-zip ssh-msg-kexinit-compression_algorithms_server_to_client)))))
(define (continue-after-discard in-state out-state)
(case role
((client) (perform-client-key-exchange (exchange-hash-info local-id
remote-id
encoded-local-algs
encoded-remote-algs)
kex-alg host-key-alg in-state out-state
continue-after-key-exchange))
((server) (error 'start-key-exchange "Server role unimplemented"))))
(define (continue-after-key-exchange shared-secret exchange-hash hash-alg in-state out-state)
(let ((session-id (if old-session-id
old-session-id ;; don't overwrite existing ID
exchange-hash))
(k-h-prefix (bit-string ((bit-string->integer shared-secret #t #f) :: (t:mpint))
(exchange-hash :: binary))))
(define (derive-key kind needed-bytes-or-false)
(let extend ((key (hash-alg (bit-string->bytes
(bit-string (k-h-prefix :: binary)
(kind :: binary)
(session-id :: binary))))))
(cond
((false? needed-bytes-or-false) key)
((>= (bytes-length key) needed-bytes-or-false) (subbytes key 0 needed-bytes-or-false))
(else
(extend (bytes-append key (hash-alg (bit-string->bytes
(bit-string (k-h-prefix :: binary)
(key :: binary))))))))))
(let-values (((newkeys-packet newkeys-message in-state out-state)
(read-message in-state out-state)))
(when (not (ssh-msg-newkeys? newkeys-message))
(disconnect-with-error/local-info `((message ,newkeys-message))
SSH_DISCONNECT_PROTOCOL_ERROR
"Expected SSH_MSG_NEWKEYS"))
(let ((out-state (write-message! (ssh-msg-newkeys) out-state)))
(finish-key-exchange session-id
(apply-negotiated-options in-state role #f derive-key
c2s-enc s2c-enc
c2s-mac s2c-mac
c2s-zip s2c-zip)
(apply-negotiated-options out-state role #t derive-key
c2s-enc s2c-enc
c2s-mac s2c-mac
c2s-zip s2c-zip))))))
(if should-discard-first-kex-packet
(let-values (((discarded-packet discarded-message in-state out-state)
(read-message in-state out-state)))
(continue-after-discard in-state out-state))
(continue-after-discard in-state out-state)))
(define (check-host-key! host-key)
(write `(TODO check-host-key! ,(hex (bit-string->bytes host-key)))) (newline) (flush-output)
(void))
(define (perform-client-key-exchange hash-info kex-alg host-key-alg in-state out-state finish)
(case kex-alg
((diffie-hellman-group14-sha1 diffie-hellman-group1-sha1)
(let ((group (if (eq? kex-alg 'diffie-hellman-group14-sha1)
dh:oakley-group-14
dh:oakley-group-2))) ;; yes, SSH's group1 == Oakley/RFC2409 group 2
(let*-values (((private-key public-key) (generate-key group))
((out-state)
(write-message! (ssh-msg-kexdh-init (bit-string->integer public-key #t #f))
out-state
#t))
((packet message in-state out-state)
(read-message in-state out-state)))
(if (not (ssh-msg-kexdh-reply? message))
(disconnect-with-error/local-info `((message ,message))
SSH_DISCONNECT_KEY_EXCHANGE_FAILED
"Unexpected packet type")
(let* ((f (ssh-msg-kexdh-reply-f message))
(f-width (mpint-width f))
(f-as-bytes (integer->bit-string f (* 8 f-width) #t))
(shared-secret (compute-key private-key f-as-bytes))
(hash-alg sha1)
(exchange-hash (dh-exchange-hash hash-info
(ssh-msg-kexdh-reply-host-key message)
(bit-string->integer public-key #t #f)
f
(bit-string->integer shared-secret #t #f))))
;; (pretty-print `((public-key ,(hex public-key))
;; (f-as-bytes ,(hex f-as-bytes))
;; (shared-secret ,(hex shared-secret))
;; (exchange-hash ,(hex exchange-hash))))
(check-host-key! (ssh-msg-kexdh-reply-host-key message))
(finish shared-secret exchange-hash hash-alg in-state out-state))))))
(else (disconnect-with-error SSH_DISCONNECT_KEY_EXCHANGE_FAILED
"Bad key-exchange algorithm ~v" kex-alg))))
(define (dh-exchange-hash hash-info host-key e f k)
(let ((block-to-hash
(bit-string->bytes
(bit-string ((string->bytes/utf-8 (exchange-hash-info-client-id hash-info)) :: (t:string))
((string->bytes/utf-8 (exchange-hash-info-server-id hash-info)) :: (t:string))
((exchange-hash-info-client-kexinit-bytes hash-info) :: (t:string))
((exchange-hash-info-server-kexinit-bytes hash-info) :: (t:string))
(host-key :: (t:string))
(e :: (t:mpint))
(f :: (t:mpint))
(k :: (t:mpint))))))
;;(pretty-print `((block-to-hash ,(hex block-to-hash))))
(sha1 block-to-hash)))
(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)))))
(let-values (((i o) (tcp-connect "localhost"
2323
;;22
)))
(ssh-session 'client
i o #f
(lambda (seed message continue-reading)
(pretty-print message)
(continue-reading (list) seed)))))
(t)

136
test-aes-ctr.rkt Normal file
View File

@ -0,0 +1,136 @@
#lang racket/base
(require "aes-ctr.rkt")
(require rackunit)
(require (planet tonyg/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"))