|
| 1 | +#!/usr/bin/env expect |
| 2 | + |
| 3 | +# Walk a NodeMCU device through some basic functionality tests. |
| 4 | +# |
| 5 | +# Requires `socat` and `openssl` on the host side; tested only on Linux. |
| 6 | +# |
| 7 | +# Tries to guess the host's IP address using `ip route get`, but this can be |
| 8 | +# overridden with the -ip command line option. |
| 9 | +# |
| 10 | +# A typical invocation looks like: |
| 11 | +# TCLLIBPATH=./expectnmcu ./tls-test.expect -serial /dev/ttyUSB3 -wifi "$(cat wificmd)" |
| 12 | +# |
| 13 | +# where the file `wificmd` contains something like |
| 14 | +# wifi.setmode(wifi.STATION); wifi.sta.config({...}); wifi.sta.connect() |
| 15 | +# where the ... is filled in with the local network's configuration. All on |
| 16 | +# one line, tho', so that the script just gets one prompt back. |
| 17 | +# |
| 18 | +# For debugging the test itself, it may be useful to invoke expect with -d, |
| 19 | +# which will give a great deal of diagnostic information about the expect state |
| 20 | +# machine's internals: |
| 21 | +# TCLLIBPATH=./expectnmcu expect -d ./tls-test.expect ... |
| 22 | + |
| 23 | + |
| 24 | +package require struct::stack |
| 25 | +package require expectnmcu::core |
| 26 | + |
| 27 | +::struct::stack ulogstack |
| 28 | +proc pushulog { new } { |
| 29 | + ulogstack push [log_user -info] |
| 30 | + log_user ${new} |
| 31 | +} |
| 32 | +proc populog { } { log_user [ulogstack pop] } |
| 33 | + |
| 34 | +proc genectls { curve pfx } { |
| 35 | + exec "openssl" "ecparam" "-genkey" "-name" ${curve} "-out" "${pfx}.key" |
| 36 | + exec "openssl" "req" "-new" "-sha256" "-subj" "/CN=${curve}" "-key" "${pfx}.key" "-out" "${pfx}.csr" |
| 37 | + exec "openssl" "req" "-x509" "-sha256" "-days" "365" "-key" "${pfx}.key" "-in" "${pfx}.csr" "-out" "${pfx}.crt" |
| 38 | +} |
| 39 | + |
| 40 | +proc preptls { victim } { |
| 41 | + ::expectnmcu::core::send_exp_prompt_c ${victim} "function tlsbasic(id,port,host)" |
| 42 | + ::expectnmcu::core::send_exp_prompt_c ${victim} " local c = tls.createConnection()" |
| 43 | + ::expectnmcu::core::send_exp_prompt_c ${victim} " c:on(\"receive\", function(sck, d) print(\"RECV\",id,d) end)" |
| 44 | + ::expectnmcu::core::send_exp_prompt_c ${victim} " c:on(\"connection\", function(sck) print(\"CONN\",id); sck:send(\"GET / HTTP/1.0\\r\\n\\r\\n\") end)" |
| 45 | + ::expectnmcu::core::send_exp_prompt_c ${victim} " c:on(\"disconnection\", function(sck) print(\"DISC\",id) end)" |
| 46 | + ::expectnmcu::core::send_exp_prompt_c ${victim} " c:connect(port,host)" |
| 47 | + ::expectnmcu::core::send_exp_prompt_c ${victim} " return c" |
| 48 | + ::expectnmcu::core::send_exp_prompt ${victim} "end" |
| 49 | +} |
| 50 | + |
| 51 | +# Basic connectivity test, including disconnection of localsid. |
| 52 | +proc basicconntest { id localsid victimsid victimconn } { |
| 53 | + set timeout 15 |
| 54 | + expect { |
| 55 | + -i ${localsid} -re ".+" { |
| 56 | + # If socat says anything, it's almost surely an error |
| 57 | + exit 1 |
| 58 | + } |
| 59 | + -i ${victimsid} "CONN\t${id}" { } |
| 60 | + } |
| 61 | + set timeout 2 |
| 62 | + pushulog 0 |
| 63 | + expect { |
| 64 | + -i ${localsid} "GET / HTTP/1.0\r\n\r\n" { |
| 65 | + send -i ${localsid} "abracadabra" |
| 66 | + } |
| 67 | + } |
| 68 | + populog |
| 69 | + expect { |
| 70 | + -i ${victimsid} "RECV\t${id}\tabracadabra" { |
| 71 | + ::expectnmcu::core::send_exp_prompt ${victimsid} "${victimconn}:send(\"test 1 2 3 4\")" |
| 72 | + } |
| 73 | + } |
| 74 | + pushulog 0 |
| 75 | + expect { |
| 76 | + -i ${localsid} "test 1 2 3 4" { |
| 77 | + close -i ${localsid} |
| 78 | + } |
| 79 | + } |
| 80 | + populog |
| 81 | + set timeout 15 |
| 82 | + expect { |
| 83 | + -i ${victimsid} "DISC\t${id}" { } |
| 84 | + } |
| 85 | +} |
| 86 | + |
| 87 | +# Generate some TLS certificates for our use, if they don't exist |
| 88 | +set fntls256v1 "test-256v1" |
| 89 | +if { ! [file exists "${fntls256v1}.key" ] } { genectls "prime256v1" ${fntls256v1} } |
| 90 | +set fntls384r1 "test-384r1" |
| 91 | +if { ! [file exists "${fntls384r1}.key" ] } { genectls "secp384r1" ${fntls384r1} } |
| 92 | + |
| 93 | +package require cmdline |
| 94 | +set cmd_parameters { |
| 95 | + { serial.arg "/dev/ttyUSB0" "Set the serial interface name" } |
| 96 | + { wifi.arg "" "Command to run to bring up the network" } |
| 97 | + { ip.arg "" "My IP address (will guess if not given)" } |
| 98 | +# { debug "Turn on debugging" } |
| 99 | +} |
| 100 | +set cmd_usage "- A NodeMCU TLS test program" |
| 101 | +if {[catch {array set cmdopts [cmdline::getoptions ::argv $cmd_parameters $cmd_usage]}]} { |
| 102 | + send_user [cmdline::usage $cmd_parameters $cmd_usage] |
| 103 | + exit 0 |
| 104 | +} |
| 105 | + |
| 106 | +# if { ${cmdopts(debug)} } { exp_internal 1 } |
| 107 | + |
| 108 | +send_user "===> Note: Serial port is ${cmdopts(serial)}; debug is ${cmdopts(debug)} <===\n" |
| 109 | + |
| 110 | +set victim [::expectnmcu::core::connect ${cmdopts(serial)} 115200] |
| 111 | +::expectnmcu::core::reboot ${cmdopts(serial)} 115200 |
| 112 | + |
| 113 | +# Wait for the system to boot |
| 114 | +::expectnmcu::core::waitboot ${victim} |
| 115 | +send_user "\n===> Machine has booted <===\n" |
| 116 | + |
| 117 | +# Program a routine for TLS connections |
| 118 | +preptls ${victim} |
| 119 | + |
| 120 | +# Connect the board to the network |
| 121 | + |
| 122 | +if {[expr 0 < [string length ${cmdopts(wifi)}]]} { |
| 123 | + ::expectnmcu::core::send_exp_prompt ${victim} ${cmdopts(wifi)} |
| 124 | +} |
| 125 | + |
| 126 | +for {set i 0} {${i} < 10} {incr i} { |
| 127 | + send -i ${victim} "=wifi.sta.getip()\n" |
| 128 | + expect { |
| 129 | + -i ${victim} -re "\n(\[^\n\t]+)\t\[^\t]+\t\[^\t]+\n> " { |
| 130 | + set victimip ${expect_out(1,string)} |
| 131 | + send_user "\n===> Victim IP address ${victimip} <===\n" |
| 132 | + break |
| 133 | + } |
| 134 | + -i ${victim} -ex "nil\r\n> " { |
| 135 | + # must not be connected |
| 136 | + sleep 1 |
| 137 | + } |
| 138 | + } |
| 139 | +} |
| 140 | +if {[expr 10 == $i]} { |
| 141 | + send_user "\n===> Unable to connect to network; bailing out! <===\n" |
| 142 | + exit 1 |
| 143 | +} |
| 144 | + |
| 145 | +if {[expr 0 < [string length ${cmdopts(ip)}]]} { |
| 146 | + set myip ${cmdopts(ip)} |
| 147 | +} else { |
| 148 | + # Guess our IP address by using the victim's |
| 149 | + spawn "ip" "route" "get" ${victimip} |
| 150 | + expect { |
| 151 | + -re "src (\[^ ]*) " { |
| 152 | + set myip ${expect_out(1,string)} |
| 153 | + } |
| 154 | + } |
| 155 | + close |
| 156 | +} |
| 157 | + |
| 158 | +::expectnmcu::core::send_exp_prompt ${victim} "tls.setDebug(2)" |
| 159 | +::expectnmcu::core::send_exp_prompt ${victim} "tls.cert.verify(false)" |
| 160 | + |
| 161 | +send_user "\n===> TEST SSL 256v1, no verify <===\n" |
| 162 | + |
| 163 | +spawn -noecho "socat" "STDIO,cfmakeraw" "OPENSSL-LISTEN:12345,verify=0,certificate=${fntls256v1}.crt,key=${fntls256v1}.key,reuseaddr" |
| 164 | +::expectnmcu::core::send_exp_prompt ${victim} "c = tlsbasic(0,12345,\"${myip}\")" |
| 165 | +basicconntest 0 ${spawn_id} ${victim} "c" |
| 166 | + |
| 167 | +send_user "\n===> TEST SSL 384r1, no verify <===\n" |
| 168 | + |
| 169 | +spawn -noecho "socat" "STDIO,cfmakeraw" "OPENSSL-LISTEN:12345,verify=0,certificate=${fntls384r1}.crt,key=${fntls384r1}.key,reuseaddr" |
| 170 | +::expectnmcu::core::send_exp_prompt ${victim} "c = tlsbasic(1,12345,\"${myip}\")" |
| 171 | +basicconntest 1 ${spawn_id} ${victim} "c" |
| 172 | + |
| 173 | +send_user "\n===> TEST SSL 384r1, verify <===\n" |
| 174 | + |
| 175 | +set cert [open "${fntls384r1}.crt"] |
| 176 | +::expectnmcu::core::send_exp_prompt_c ${victim} "tls.cert.verify(\[\[" |
| 177 | +while { [gets $cert line] >= 0 } { |
| 178 | + ::expectnmcu::core::send_exp_prompt_c ${victim} $line |
| 179 | +} |
| 180 | +::expectnmcu::core::send_exp_prompt ${victim} "]])" |
| 181 | +close ${cert} |
| 182 | +::expectnmcu::core::send_exp_prompt ${victim} "tls.cert.verify(true)" |
| 183 | + |
| 184 | +spawn -noecho "socat" "STDIO,cfmakeraw" "OPENSSL-LISTEN:12345,verify=0,certificate=${fntls384r1}.crt,key=${fntls384r1}.key,reuseaddr" |
| 185 | +::expectnmcu::core::send_exp_prompt ${victim} "c = tlsbasic(2,12345,\"${myip}\")" |
| 186 | +basicconntest 2 ${spawn_id} ${victim} "c" |
| 187 | + |
| 188 | +send_user "\n===> TESTS OK <===\n" |
0 commit comments