From 24b777bf50397433bc5ec19862dff67b3507c711 Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Sun, 22 Dec 2019 13:19:29 +0000 Subject: [PATCH] TLS: add first pass of test program --- lua_tests/tls-test.expect | 226 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 lua_tests/tls-test.expect diff --git a/lua_tests/tls-test.expect b/lua_tests/tls-test.expect new file mode 100644 index 0000000000..3aa2cc585d --- /dev/null +++ b/lua_tests/tls-test.expect @@ -0,0 +1,226 @@ +#!/usr/bin/env expect + +# Walk a NodeMCU device through some basic functionality tests. +# +# Requires `socat` and `openssl` on the host side; tested only on Linux. +# +# Tries to guess the host's IP address using `ip route get`, but this can be +# overridden with the -ip command line option. +# +# A typical invocation looks like: +# ./tls-test.expect -serial /dev/ttyUSB3 -wifi "$(cat wificmd)" +# +# where the file `wificmd` contains something like +# wifi.setmode(wifi.STATION); wifi.sta.config({...}); wifi.sta.connect() +# where the ... is filled in with the local network's configuration. All on +# one line, tho', so that the script just gets one prompt back. +# +# For debugging the test itself, it may be useful to invoke expect with -d, +# which will give a great deal of diagnostic information about the expect state +# machine's internals: +# expect -d ./tls-test.expect ... + + +package require struct::stack + +::struct::stack ulogstack +proc pushulog { new } { + ulogstack push [log_user -info] + log_user ${new} +} +proc populog { } { log_user [ulogstack pop] } + +proc send_exp_prompt { sid cmd } { + send -i ${sid} -- "${cmd}\n" + expect { + -i ${sid} -ex "\n> " { } + } +} + +proc send_exp_prompt_c { sid cmd } { + send -i ${sid} -- "${cmd}\n" + expect { + -i ${sid} -ex "\n>> " { } + } +} + +proc genectls { curve pfx } { + exec "openssl" "ecparam" "-genkey" "-name" ${curve} "-out" "${pfx}.key" + exec "openssl" "req" "-new" "-sha256" "-subj" "/CN=${curve}" "-key" "${pfx}.key" "-out" "${pfx}.csr" + exec "openssl" "req" "-x509" "-sha256" "-days" "365" "-key" "${pfx}.key" "-in" "${pfx}.csr" "-out" "${pfx}.crt" +} + +proc preptls { victim } { + send_exp_prompt_c ${victim} "function tlsbasic(id,port,host)" + send_exp_prompt_c ${victim} " local c = tls.createConnection()" + send_exp_prompt_c ${victim} " c:on(\"receive\", function(sck, d) print(\"RECV\",id,d) end)" + send_exp_prompt_c ${victim} " c:on(\"connection\", function(sck) print(\"CONN\",id); sck:send(\"GET / HTTP/1.0\\r\\n\\r\\n\") end)" + send_exp_prompt_c ${victim} " c:on(\"disconnection\", function(sck) print(\"DISC\",id) end)" + send_exp_prompt_c ${victim} " c:connect(port,host)" + send_exp_prompt_c ${victim} " return c" + send_exp_prompt ${victim} "end" +} + +# Basic connectivity test, including disconnection of localsid. +proc basicconntest { id localsid victimsid victimconn } { + set timeout 15 + expect { + -i ${localsid} -re ".+" { + # If socat says anything, it's almost surely an error + exit 1 + } + -i ${victimsid} "CONN\t${id}" { } + } + set timeout 2 + pushulog 0 + expect { + -i ${localsid} "GET / HTTP/1.0\r\n\r\n" { + send -i ${localsid} "abracadabra" + } + } + populog + expect { + -i ${victimsid} "RECV\t${id}\tabracadabra" { + send_exp_prompt ${victimsid} "${victimconn}:send(\"test 1 2 3 4\")" + } + } + pushulog 0 + expect { + -i ${localsid} "test 1 2 3 4" { + close -i ${localsid} + } + } + populog + set timeout 15 + expect { + -i ${victimsid} "DISC\t${id}" { } + } +} + +# Generate some TLS certificates for our use, if they don't exist +set fntls256v1 "test-256v1" +if { ! [file exists "${fntls256v1}.key" ] } { genectls "prime256v1" ${fntls256v1} } +set fntls384r1 "test-384r1" +if { ! [file exists "${fntls384r1}.key" ] } { genectls "secp384r1" ${fntls384r1} } + +package require cmdline +set cmd_parameters { + { serial.arg "/dev/ttyUSB0" "Set the serial interface name" } + { wifi.arg "" "Command to run to bring up the network" } + { ip.arg "" "My IP address (will guess if not given)" } +# { debug "Turn on debugging" } +} +set cmd_usage "- A NodeMCU TLS test program" +if {[catch {array set cmdopts [cmdline::getoptions ::argv $cmd_parameters $cmd_usage]}]} { + send_user [cmdline::usage $cmd_parameters $cmd_usage] + exit 0 +} + +# if { ${cmdopts(debug)} } { exp_internal 1 } + +send_user "===> Note: Serial port is ${cmdopts(serial)}; debug is ${cmdopts(debug)} <===\n" + +spawn "socat" "STDIO" "${cmdopts(serial)},rawer,crnl" +set victim ${spawn_id} +close -onexec 1 -i ${victim} + +# Use DTR/RTS signaling to reboot the device +## I'm not sure why we have to keep resetting the mode, but so it goes. +set victimfd [open "${cmdopts(serial)}"] +fconfigure ${victimfd} -mode 115200,n,8,1 -ttycontrol {DTR 0 RTS 1} +sleep 0.1 +fconfigure ${victimfd} -mode 115200,n,8,1 -ttycontrol {DTR 0 RTS 0} +close ${victimfd} + +# Wait for the system to boot +expect { + -i ${victim} "Formatting file system" { + set timeout 60 + exp_continue + } + -i ${victim} "powered by Lua" { } + timeout { + send_user "\n===> Did not see boot sequence; bailing out <===\n" + exit 0 + } +} +# Catch nwf's system bootup, in case we're testing an existing system, +# rather than a blank firmware. +expect { + -i ${victim} "Reset delay!" { send "got:stop()\n" ; expect "> " } + -i ${victim} "> " { } +} +send_user "\n===> Machine has booted <===\n" + +# Program a routine for TLS connections +preptls ${victim} + +# Connect the board to the network + +if {[expr 0 < [string length ${cmdopts(wifi)}]]} { + send_exp_prompt ${victim} ${cmdopts(wifi)} +} + +for {set i 0} {${i} < 10} {incr i} { + send -i ${victim} "=wifi.sta.getip()\n" + expect { + -i ${victim} -re "\n(\[^\n\t]+)\t\[^\t]+\t\[^\t]+\n> " { + set victimip ${expect_out(1,string)} + send_user "\n===> Victim IP address ${victimip} <===\n" + break + } + -i ${victim} -ex "nil\r\n> " { + # must not be connected + sleep 1 + } + } +} +if {[expr 10 == $i]} { + send_user "\n===> Unable to connect to network; bailing out! <===\n" + exit 1 +} + +if {[expr 0 < [string length ${cmdopts(ip)}]]} { + set myip ${cmdopts(ip)} +} else { + # Guess our IP address by using the victim's + spawn "ip" "route" "get" ${victimip} + expect { + -re "src (\[^ ]*) " { + set myip ${expect_out(1,string)} + } + } + close +} + +send_exp_prompt ${victim} "tls.setDebug(2)" +send_exp_prompt ${victim} "tls.cert.verify(false)" + +send_user "\n===> TEST SSL 256v1, no verify <===\n" + +spawn -noecho "socat" "STDIO,cfmakeraw" "OPENSSL-LISTEN:12345,verify=0,certificate=${fntls256v1}.crt,key=${fntls256v1}.key,reuseaddr" +send_exp_prompt ${victim} "c = tlsbasic(0,12345,\"${myip}\")" +basicconntest 0 ${spawn_id} ${victim} "c" + +send_user "\n===> TEST SSL 384r1, no verify <===\n" + +spawn -noecho "socat" "STDIO,cfmakeraw" "OPENSSL-LISTEN:12345,verify=0,certificate=${fntls384r1}.crt,key=${fntls384r1}.key,reuseaddr" +send_exp_prompt ${victim} "c = tlsbasic(1,12345,\"${myip}\")" +basicconntest 1 ${spawn_id} ${victim} "c" + +send_user "\n===> TEST SSL 384r1, verify <===\n" + +set cert [open "${fntls384r1}.crt"] +send_exp_prompt_c ${victim} "tls.cert.verify(\[\[" +while { [gets $cert line] >= 0 } { + send_exp_prompt_c ${victim} $line +} +send_exp_prompt ${victim} "]])" +close ${cert} +send_exp_prompt ${victim} "tls.cert.verify(true)" + +spawn -noecho "socat" "STDIO,cfmakeraw" "OPENSSL-LISTEN:12345,verify=0,certificate=${fntls384r1}.crt,key=${fntls384r1}.key,reuseaddr" +send_exp_prompt ${victim} "c = tlsbasic(2,12345,\"${myip}\")" +basicconntest 2 ${spawn_id} ${victim} "c" + +send_user "\n===> TESTS OK <===\n"