├── .gitignore ├── README.md └── bash_tls.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .sw? 3 | .*.sw? 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bash_tls 2 | A minimal TLS 1.2 implementation in a pure Bash script 3 | 4 | bash_tls implements TLS well enough to make a simple HTTPS request to most web servers. 5 | 6 | ## Usage 7 | `./bash_tls.sh [https://website.com/path/to/file]` 8 | If a URL is not given, defaults to `https://www.google.com/robots.txt`. 9 | 10 | ## Features 11 | * Supports a single cipher suite: TLS_RSA_WITH_AES_128_GCM_SHA256 12 | * RSA key exchange 13 | * HMAC-SHA256 as pseudorandom function 14 | * AES in GCM mode for encryption 15 | * Supports Server Name Indication 16 | 17 | ## Missing 18 | * Only supports RSA certificates 19 | * Does not validate certificate chain 20 | 21 | ## Dependencies 22 | bash_tls depends only on the following software: 23 | * bash 4.3+ compiled with `--enable-net-redirections` 24 | * GNU bc (for doing RSA calculations) 25 | * sha256sum (from Linux coreutils) or shasum (on Mac) 26 | 27 | ## Performance 28 | Don't ask. 29 | -------------------------------------------------------------------------------- /bash_tls.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Dependencies 4 | # - bash 4.3+ 5 | # - bc (for RSA calculations) 6 | # - sha256sum (for pseudorandom function) 7 | 8 | set -eu 9 | set -o pipefail 10 | set -o errtrace 11 | trap 'die "command error"' ERR 12 | 13 | if [ "$BASH_VERSION" '<' '4.3' ] || (( 64 << 30 < 64 )); then 14 | echo "Only bash 4.3+ with 64-bit integers is supported." >&2 15 | exit 1 16 | fi 17 | 18 | ######################################## 19 | ## Utilities 20 | ######################################## 21 | 22 | die() { 23 | local msg="$1" 24 | echo "FATAL ERROR: $msg">&2 25 | 26 | local i=0 info 27 | while info=$(caller $i); do 28 | set -- $info 29 | local line=$1 func=$2 file=$3 30 | printf '\t%s at %s:%s\n' "$func" "$file" "$line" >&2 31 | (( i += 1 )) 32 | done 33 | 34 | kill -ABRT -$$ 35 | } 36 | 37 | hex_iter() { 38 | local str=$1 39 | local i=0 j=${#str} 40 | while (( i < j )); do 41 | echo ${str:i:2} 42 | (( i += 2 )) 43 | done 44 | } 45 | 46 | hex_len() { 47 | local str=${1} 48 | echo $((${#str} / 2)) 49 | } 50 | 51 | hex_slice_lenient() { 52 | local str=$1 off=$2 len=${3:-all} 53 | if [ "$len" = "all" ]; then 54 | echo ${str:off * 2} 55 | else 56 | echo ${str:off * 2:len * 2} 57 | fi 58 | } 59 | 60 | hex_slice() { 61 | local str=$1 off=$2 len=${3:-all} 62 | if (( off * 2 > ${#str} )); then 63 | die 'bad slice' 64 | fi 65 | if [ "$len" = "all" ]; then 66 | echo ${str:off * 2} 67 | else 68 | if (( (off + len) * 2 > ${#str} )); then 69 | die 'bad slice' 70 | fi 71 | echo ${str:off * 2:len * 2} 72 | fi 73 | } 74 | 75 | hex_consume() { 76 | local -n _hs_consume_out=$1 _hs_consume_src=$2 77 | local i=$3 78 | if (( ${#_hs_consume_src} < i * 2 )); then 79 | die 'too little data to consume' 80 | fi 81 | _hs_consume_out=${_hs_consume_src:0:i * 2} 82 | _hs_consume_src=${_hs_consume_src:i * 2} 83 | } 84 | 85 | hex_pop() { 86 | local -n _hs_pop_out=$1 _hs_pop_src=$2 87 | local i=$3 88 | local j=$((${#_hs_pop_src} - i * 2)) 89 | if (( j < 0 )); then 90 | die 'too little data to pop' 91 | fi 92 | _hs_pop_out=${_hs_pop_src:j} 93 | _hs_pop_src=${_hs_pop_src:0:j} 94 | } 95 | 96 | hex_int() { 97 | local hex=$1 98 | echo $((0x$hex)) 99 | } 100 | 101 | hex_strip_leading_zeroes() { 102 | local hex=$1 103 | while [ "${hex:0:2}" == 00 ]; do hex=${hex:2}; done 104 | echo "$hex" 105 | } 106 | 107 | hex_convert_from_string() { 108 | local str=$1 out= 109 | local i=0 110 | while (( i < ${#str} )); do 111 | out+=$(printf %02X "'${str:i:1}") 112 | (( i += 1 )) 113 | done 114 | echo $out 115 | } 116 | 117 | write_to_fd() { 118 | local fd=$1 str=$2 119 | local LC_ALL=C 120 | local hc hd 121 | for hc in $(hex_iter $str); do hd+="\x$hc"; done 122 | echo -ne "$hd" >&${fd} 123 | } 124 | 125 | read_from_fd() { 126 | local fd=$1 size=$2 127 | local LC_ALL=C 128 | local i c 129 | for i in $(eval echo {1..$size}); do 130 | IFS= read -n 1 -d '' -u $fd -r c || die 'socket closed' 131 | printf '%02X' "'$c" 132 | done 133 | } 134 | 135 | sha256_digest() { 136 | local data=$1 137 | local COPROC digest 138 | if command -v sha256sum >&${devnullfd}; then 139 | coproc sha256sum 140 | elif command -v shasum >&${devnullfd}; then 141 | coproc shasum -a256 142 | else 143 | die 'neither sha256sum nor shasum could be found' 144 | fi 145 | local outfd=${COPROC[0]} infd=${COPROC[1]} 146 | write_to_fd $infd $data 147 | exec {infd}>&- 148 | read -d ' ' -u $outfd digest 149 | exec {outfd}<&- 150 | echo ${digest^^} 151 | } 152 | 153 | ######################################## 154 | ## GF2 arithmetic 155 | ######################################## 156 | 157 | gf2_simplify() { 158 | # chop off leading zeros 159 | local -n _gf2_simp_x=$1 160 | while (( ${#_gf2_simp_x[@]} && _gf2_simp_x[-1] == 0 )); do 161 | unset '_gf2_simp_x[-1]' 162 | done 163 | } 164 | 165 | gf2_chkbit() { 166 | local -n _gf2_chkbit_x=$1 167 | local bit_number=$2 168 | local i=${_gf2_chkbit_x[bit_number / 32]:-0} 169 | return $((!(i & 1 << (bit_number % 32)))) # sh return codes are inverted 170 | } 171 | 172 | gf2_add() { # xor in GF2 173 | local -n _gf2_add_dst=$1 174 | local -n _gf2_add_src=$2 175 | local should_simplify=${3:-1} 176 | local i=0 177 | while (( i < ${#_gf2_add_src[@]} )); do 178 | local ai=${_gf2_add_dst[i]:-0} bi=${_gf2_add_src[i]} 179 | _gf2_add_dst[i]=$((ai ^ bi)) 180 | (( i += 1 )) 181 | done 182 | if (( should_simplify )); then 183 | gf2_simplify _gf2_add_dst 184 | fi 185 | } 186 | 187 | gf2_mulx() { 188 | local -n _gf2_mulx_dst=$1 189 | local -n _gf2_mulx_mod=$2 190 | local i=0 si=${_gf2_mulx_dst[0]:-0} 191 | while (( i < ${#_gf2_mulx_dst[@]} )); do 192 | local ai=${_gf2_mulx_dst[i]} bi=${_gf2_mulx_dst[i+1]:-0} 193 | _gf2_mulx_dst[i]=$(((bi << 31 | ai >> 1) & 0xFFFFFFFF)) 194 | (( i += 1 )) 195 | done 196 | if (( si & 0x1 )); then 197 | gf2_add _gf2_mulx_dst _gf2_mulx_mod 198 | else 199 | gf2_simplify _gf2_mulx_dst 200 | fi 201 | } 202 | 203 | gf2_lshift() { 204 | local -n _gf2_val=$1 205 | local shift_amt=$2 206 | while (( shift_amt >= 32 )); do 207 | _gf2_val=(0 ${_gf2_val[@]}) 208 | (( shift_amt -= 32 )) || true 209 | done 210 | if (( shift_amt )); then 211 | local i=${#_gf2_val[@]} 212 | while (( i >= 0 )); do 213 | local ai=${_gf2_val[i]:-0} 214 | if (( i > 0 )); then 215 | local bi=${_gf2_val[i-1]} 216 | else 217 | local bi=0 218 | fi 219 | local ci=$(((ai << shift_amt | bi >> (32 - shift_amt)) & 0xFFFFFFFF)) 220 | if (( ci || i < ${#_gf2_val[@]} )); then 221 | _gf2_val[i]=$ci 222 | fi 223 | (( i -= 1 )) || true 224 | done 225 | fi 226 | } 227 | 228 | gf2_rshift() { 229 | local -n _gf2_val=$1 230 | local shift_amt=$2 231 | while (( shift_amt >= 32 )); do 232 | unset '_gf2_val[0]' 233 | (( shift_amt -= 32 )) || true 234 | done 235 | if (( shift_amt )); then 236 | local i=0 237 | while (( i < ${#_gf2_val[@]} )); do 238 | local ai=${_gf2_val[i]} bi=${_gf2_val[i+1]:-0} 239 | _gf2_val[i]=$(((bi << (32 - shift_amt) | ai >> shift_amt) & 0xFFFFFFFF)) 240 | (( i += 1 )) 241 | done 242 | gf2_simplify _gf2_val 243 | fi 244 | } 245 | 246 | gf2_dump_to_stderr() { 247 | local -n _gf2_dump_val=$1 248 | local i out= 249 | for i in $(eval echo {0..$((${#_gf2_dump_val[@]}-1))}); do 250 | out="$(printf %08X ${_gf2_dump_val[i]})${out}" 251 | done 252 | echo $out >&2 253 | } 254 | 255 | gf2_from_hex() { 256 | local hex=$1 257 | local out= 258 | while [ -n "$hex" ]; do 259 | local msb=${hex:0:8} 260 | hex=${hex:8} 261 | out="$((0x$msb)) $out" 262 | done 263 | echo $out 264 | } 265 | 266 | gf2_to_hex() { 267 | local -n _gf2_th_val=$1 268 | local i out= 269 | for i in $(eval echo {0..$((${#_gf2_th_val[@]}-1))}); do 270 | out="$(printf %08X ${_gf2_th_val[i]})${out}" 271 | done 272 | echo "$out" 273 | } 274 | 275 | ######################################## 276 | ## ASN.1 277 | ######################################## 278 | 279 | asn1_get_off_len() { 280 | local node=$1 281 | local header=$(hex_int $(hex_slice "$node" 0 1)) 282 | if (( (header & 0x1F) == 0x1F )); then 283 | die 'unsupported asn.1 tag' 284 | fi 285 | local offset=2 286 | local length=$(hex_int $(hex_slice "$node" 1 1)) 287 | if (( length & 0x80 )); then 288 | (( length &= 0x7F, offset += length )) 289 | length=$(hex_int $(hex_slice "$node" 2 $length)) 290 | fi 291 | echo $offset $length 292 | } 293 | 294 | asn1_enter() { 295 | local node=$1 296 | set -- $(asn1_get_off_len "$node") 297 | local offset=$1 length=$2 298 | hex_slice "$node" $offset $length 299 | } 300 | 301 | asn1_skip() { 302 | local node=$1 303 | set -- $(asn1_get_off_len "$node") 304 | local offset=$1 length=$2 305 | hex_slice "$node" $((offset + length)) 306 | } 307 | 308 | ######################################## 309 | ## AES 310 | ######################################## 311 | 312 | AES_S_BOX=(99 124 119 123 242 107 111 197 48 1 103 43 254 215 171 118 202 130 201 125 250 89 71 240 173 212 162 175 156 164 114 192 183 253 147 38 54 63 247 204 52 165 229 241 113 216 49 21 4 199 35 195 24 150 5 154 7 18 128 226 235 39 178 117 9 131 44 26 27 110 90 160 82 59 214 179 41 227 47 132 83 209 0 237 32 252 177 91 106 203 190 57 74 76 88 207 208 239 170 251 67 77 51 133 69 249 2 127 80 60 159 168 81 163 64 143 146 157 56 245 188 182 218 33 16 255 243 210 205 12 19 236 95 151 68 23 196 167 126 61 100 93 25 115 96 129 79 220 34 42 144 136 70 238 184 20 222 94 11 219 224 50 58 10 73 6 36 92 194 211 172 98 145 149 228 121 231 200 55 109 141 213 78 169 108 86 244 234 101 122 174 8 186 120 37 46 28 166 180 198 232 221 116 31 75 189 139 138 112 62 181 102 72 3 246 14 97 53 87 185 134 193 29 158 225 248 152 17 105 217 142 148 155 30 135 233 206 85 40 223 140 161 137 13 191 230 66 104 65 153 45 15 176 84 187 22) 313 | AES_A_MIX=(0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60 62 64 66 68 70 72 74 76 78 80 82 84 86 88 90 92 94 96 98 100 102 104 106 108 110 112 114 116 118 120 122 124 126 128 130 132 134 136 138 140 142 144 146 148 150 152 154 156 158 160 162 164 166 168 170 172 174 176 178 180 182 184 186 188 190 192 194 196 198 200 202 204 206 208 210 212 214 216 218 220 222 224 226 228 230 232 234 236 238 240 242 244 246 248 250 252 254 27 25 31 29 19 17 23 21 11 9 15 13 3 1 7 5 59 57 63 61 51 49 55 53 43 41 47 45 35 33 39 37 91 89 95 93 83 81 87 85 75 73 79 77 67 65 71 69 123 121 127 125 115 113 119 117 107 105 111 109 99 97 103 101 155 153 159 157 147 145 151 149 139 137 143 141 131 129 135 133 187 185 191 189 179 177 183 181 171 169 175 173 163 161 167 165 219 217 223 221 211 209 215 213 203 201 207 205 195 193 199 197 251 249 255 253 243 241 247 245 235 233 239 237 227 225 231 229) 314 | AES_B_MIX=(0 3 6 5 12 15 10 9 24 27 30 29 20 23 18 17 48 51 54 53 60 63 58 57 40 43 46 45 36 39 34 33 96 99 102 101 108 111 106 105 120 123 126 125 116 119 114 113 80 83 86 85 92 95 90 89 72 75 78 77 68 71 66 65 192 195 198 197 204 207 202 201 216 219 222 221 212 215 210 209 240 243 246 245 252 255 250 249 232 235 238 237 228 231 226 225 160 163 166 165 172 175 170 169 184 187 190 189 180 183 178 177 144 147 150 149 156 159 154 153 136 139 142 141 132 135 130 129 155 152 157 158 151 148 145 146 131 128 133 134 143 140 137 138 171 168 173 174 167 164 161 162 179 176 181 182 191 188 185 186 251 248 253 254 247 244 241 242 227 224 229 230 239 236 233 234 203 200 205 206 199 196 193 194 211 208 213 214 223 220 217 218 91 88 93 94 87 84 81 82 67 64 69 70 79 76 73 74 107 104 109 110 103 100 97 98 115 112 117 118 127 124 121 122 59 56 61 62 55 52 49 50 35 32 37 38 47 44 41 42 11 8 13 14 7 4 1 2 19 16 21 22 31 28 25 26) 315 | 316 | aes_break_word() { 317 | local -n _out=$1 318 | local word=$2 319 | _out=($((word >> 24 & 0xFF)) $((word >> 16 & 0xFF)) $((word >> 8 & 0xFF)) $((word & 0xFF))) 320 | } 321 | 322 | aes_sub_word() { 323 | local -n _word=$1 324 | local -n box=${2:-AES_S_BOX} 325 | local x 326 | aes_break_word x $_word 327 | _word=$((box[x[0]] << 24 | box[x[1]] << 16 | box[x[2]] << 8 | box[x[3]])) 328 | } 329 | 330 | aes_init_state() { 331 | local self=$1 332 | local key=$2 333 | 334 | local Nk=$(($(hex_len $key) / 4)) 335 | local Nr=$((Nk + 6)) 336 | local rcon=1 337 | 338 | local -n self_w=${self}_w 339 | self_w=() 340 | local -n self_Nr=${self}_Nr 341 | self_Nr=$Nr 342 | 343 | while [ -n "$key" ]; do 344 | local chunk 345 | hex_consume chunk key 4 346 | self_w+=($(hex_int $chunk)) 347 | done 348 | 349 | while (( (i=${#self_w[@]}) < 4 * (Nr + 1) )); do 350 | local temp=${self_w[-1]} 351 | if (( i % Nk == 0 )); then 352 | temp=$(((temp << 8 | temp >> 24) & 0xFFFFFFFF)) 353 | aes_sub_word temp 354 | temp=$((temp ^ (rcon << 24))) 355 | rcon=$(((rcon << 1) ^ (0x11B & -(rcon >> 7)))) 356 | elif (( Nk > 6 && i % Nk == 4 )); then 357 | aes_sub_word temp 358 | fi 359 | self_w+=($((self_w[-Nk] ^ temp))) 360 | done 361 | } 362 | 363 | aes_do_encrypt() { 364 | local self=$1 365 | local block=$2 366 | 367 | local -n self_Nr=${self}_Nr 368 | 369 | local i=0 370 | local state=($(hex_int $(hex_slice $block 0 4)) 371 | $(hex_int $(hex_slice $block 4 4)) 372 | $(hex_int $(hex_slice $block 8 4)) 373 | $(hex_int $(hex_slice $block 12 4))) 374 | 375 | aes_step_add_round_key state $self 0 376 | while ((i++ < self_Nr)); do 377 | aes_step_sub_bytes state 378 | aes_step_shift_rows state 379 | ((i < self_Nr)) && aes_step_mix_columns state 380 | aes_step_add_round_key state $self $i 381 | done 382 | printf '%08X%08X%08X%08X\n' ${state[0]} ${state[1]} ${state[2]} ${state[3]} 383 | } 384 | 385 | aes_step_add_round_key() { 386 | local -n _aes_state=$1 387 | local self=$2 388 | local round=$3 389 | 390 | local -n self_w=${self}_w 391 | 392 | (( 393 | _aes_state[0] ^= self_w[4*round+0], 394 | _aes_state[1] ^= self_w[4*round+1], 395 | _aes_state[2] ^= self_w[4*round+2], 396 | _aes_state[3] ^= self_w[4*round+3], 397 | 1 398 | )) 399 | 400 | } 401 | 402 | aes_step_sub_bytes() { 403 | local -n _aes_state=$1 404 | aes_sub_word '_aes_state[0]' 405 | aes_sub_word '_aes_state[1]' 406 | aes_sub_word '_aes_state[2]' 407 | aes_sub_word '_aes_state[3]' 408 | } 409 | 410 | aes_step_shift_rows() { 411 | local -n _aes_state=$1 412 | local s_0 s_1 s_2 s_3 413 | aes_break_word s_0 ${_aes_state[0]} 414 | aes_break_word s_1 ${_aes_state[1]} 415 | aes_break_word s_2 ${_aes_state[2]} 416 | aes_break_word s_3 ${_aes_state[3]} 417 | 418 | (( 419 | _aes_state[0] = s_0[0] << 24 | s_1[1] << 16 | s_2[2] << 8 | s_3[3], 420 | _aes_state[1] = s_1[0] << 24 | s_2[1] << 16 | s_3[2] << 8 | s_0[3], 421 | _aes_state[2] = s_2[0] << 24 | s_3[1] << 16 | s_0[2] << 8 | s_1[3], 422 | _aes_state[3] = s_3[0] << 24 | s_0[1] << 16 | s_1[2] << 8 | s_2[3], 423 | 1 424 | )) 425 | } 426 | 427 | aes_step_mix_columns() { 428 | local -n _aes_state=$1 429 | local i 430 | for i in 0 1 2 3; do 431 | local w=${_aes_state[i]} 432 | local x; aes_break_word x $w 433 | local a=($((AES_A_MIX[x[0]])) $((AES_A_MIX[x[1]])) $((AES_A_MIX[x[2]])) $((AES_A_MIX[x[3]]))) 434 | local b=($((AES_B_MIX[x[0]])) $((AES_B_MIX[x[1]])) $((AES_B_MIX[x[2]])) $((AES_B_MIX[x[3]]))) 435 | _aes_state[$i]=$(((a[0] ^ b[1] ^ x[2] ^ x[3]) << 24 | 436 | (a[1] ^ b[2] ^ x[3] ^ x[0]) << 16 | 437 | (a[2] ^ b[3] ^ x[0] ^ x[1]) << 8 | 438 | (a[3] ^ b[0] ^ x[1] ^ x[2]))) 439 | done 440 | } 441 | 442 | ######################################## 443 | ## GCM 444 | ######################################## 445 | 446 | AES_GCM_MULX_MOD=(0 0 0 3774873600) 447 | AES_GCM_REDUCTION_TABLE=(0 450 900 582 1800 1738 1164 1358 3600 4050 3476 3158 2328 2266 2716 2910 7200 7650 8100 7782 6952 6890 6316 6510 4656 5106 4532 4214 5432 5370 5820 6014 14400 14722 15300 14854 16200 16010 15564 15630 13904 14226 13780 13334 12632 12442 13020 13086 9312 9634 10212 9766 9064 8874 8428 8494 10864 11186 10740 10294 11640 11450 12028 12094 28800 28994 29444 29382 30600 30282 29708 30158 32400 32594 32020 31958 31128 30810 31260 31710 27808 28002 28452 28390 27560 27242 26668 27118 25264 25458 24884 24822 26040 25722 26172 26622 18624 18690 19268 19078 20424 19978 19532 19854 18128 18194 17748 17558 16856 16410 16988 17310 21728 21794 22372 22182 21480 21034 20588 20910 23280 23346 22900 22710 24056 23610 24188 24510 57600 57538 57988 58182 58888 59338 58764 58446 61200 61138 60564 60758 59416 59866 60316 59998 64800 64738 65188 65382 64040 64490 63916 63598 62256 62194 61620 61814 62520 62970 63420 63102 55616 55426 56004 56070 56904 57226 56780 56334 55120 54930 54484 54550 53336 53658 54236 53790 50528 50338 50916 50982 49768 50090 49644 49198 52080 51890 51444 51510 52344 52666 53244 52798 37248 36930 37380 37830 38536 38730 38156 38094 40848 40530 39956 40406 39064 39258 39708 39646 36256 35938 36388 36838 35496 35690 35116 35054 33712 33394 32820 33270 33976 34170 34620 34558 43456 43010 43588 43910 44744 44810 44364 44174 42960 42514 42068 42390 41176 41242 41820 41630 46560 46114 46692 47014 45800 45866 45420 45230 48112 47666 47220 47542 48376 48442 49020 48830) 448 | 449 | aes_gcm_init_state() { 450 | local self=$1 451 | local key=$2 452 | local iv=$3 453 | 454 | local -n self_iv=${self}_iv 455 | self_iv=$iv 456 | 457 | aes_init_state ${self}_aes $key 458 | 459 | # generate multiplication table 460 | local agis_m_hex=$(aes_do_encrypt ${self}_aes 00000000000000000000000000000000) 461 | local agis_m=($(gf2_from_hex $agis_m_hex)) 462 | 463 | local i s 464 | for i in {0..255}; do 465 | local -n mult_table_entry=${self}_mt${i} 466 | mult_table_entry=() 467 | done 468 | for s in {7..0}; do 469 | for i in {0..255}; do 470 | if (( i & (1 << s) )); then 471 | local -n mult_table_entry=${self}_mt${i} 472 | gf2_add mult_table_entry agis_m 473 | fi 474 | done 475 | gf2_mulx agis_m AES_GCM_MULX_MOD 476 | done 477 | } 478 | 479 | aes_gcm_do_encrypt() { 480 | local self=$1 nonce=$2 header=$3 fragment=$4 481 | local -n self_iv=${self}_iv 482 | set -- $(aes_gcm_inner_crypt ${self} ${self_iv}${nonce} "${header}" "${fragment}" 0) 483 | local IFS= 484 | echo "$*" 485 | } 486 | 487 | aes_gcm_do_decrypt() { 488 | local self=$1 nonce=$2 header=$3 fragment=$4 489 | local -n self_iv=${self}_iv 490 | local given_tag 491 | hex_pop given_tag fragment 16 492 | set -- $(aes_gcm_inner_crypt ${self} ${self_iv}${nonce} "${header}" "${fragment}" 1) 493 | if (( $# == 2 )); then 494 | local fragment=$1 expected_tag=$2 495 | else 496 | local fragment= expected_tag=$1 497 | fi 498 | [ $given_tag == $expected_tag ] || die 'bad tag' 499 | echo "$fragment" 500 | } 501 | 502 | aes_gcm_inner_crypt() { 503 | local self=$1 iv=$2 addl=$3 data=$4 decrypt=$5 504 | local agic_hash_state=() newdata= 505 | local Ji=1 506 | 507 | local i=0 aj=$(hex_len "$addl") 508 | while (( i < aj )); do 509 | aes_gcm_hash_block agic_hash_state $(hex_slice_lenient $addl $i 16) 510 | (( i += 16 )) 511 | done 512 | 513 | local i=0 dj=$(hex_len "$data") 514 | while (( i < dj )); do 515 | (( Ji += 1 )) 516 | printf "[%4u/%4u]" $((i/16)) $(((dj+15)/16)) >&2 517 | local chunk=$(hex_slice_lenient "$data" $i 16) newchunk= 518 | local smudge=$(aes_do_encrypt ${self}_aes ${iv}$(printf %08X $Ji)) 519 | if (( decrypt )); then 520 | aes_gcm_hash_block agic_hash_state ${chunk} 521 | fi 522 | while [ -n "$chunk" ]; do 523 | local chunk_byte smudge_byte newchunk_append 524 | hex_consume chunk_byte chunk 1 525 | hex_consume smudge_byte smudge 1 526 | printf -v newchunk_append %02X $((0x$chunk_byte ^ 0x$smudge_byte)) 527 | newchunk+=${newchunk_append} 528 | done 529 | if ! (( decrypt )); then 530 | aes_gcm_hash_block agic_hash_state ${newchunk} 531 | fi 532 | newdata+=$newchunk 533 | printf $'\b\b\b\b\b\b\b\b\b\b\b' >&2 534 | (( i += 16 )) 535 | done 536 | 537 | aes_gcm_hash_block agic_hash_state $(printf %016X $((aj * 8)))$(printf %016X $((dj * 8))) 538 | 539 | local tag_smudge=($(gf2_from_hex $(aes_do_encrypt ${self}_aes ${iv}00000001))) 540 | gf2_add agic_hash_state tag_smudge 541 | echo ${newdata} $(gf2_to_hex agic_hash_state) 542 | } 543 | 544 | aes_gcm_hash_block() { 545 | local -n aghb_state=$1 546 | local hex_chunk=$2 547 | local hex_chunk_len=$(hex_len "$hex_chunk") 548 | 549 | # pad to 16 bytes 550 | while (( hex_chunk_len < 16 )); do 551 | hex_chunk+=00 552 | (( hex_chunk_len += 1 )) 553 | done 554 | 555 | local aghb_old_state=(${aghb_state[@]}) 556 | aghb_state=() 557 | 558 | local i 559 | for i in {0..15}; do 560 | local hex_byte 561 | hex_pop hex_byte hex_chunk 1 562 | local aghb_low=${aghb_old_state[0]:-0} 563 | local -n mult_table_entry=${self}_mt$((0x$hex_byte ^ (aghb_low & 0xFF))) 564 | local mt_const=(${mult_table_entry[@]}) # copy mult_table_entry 565 | gf2_lshift mt_const $((8 * i)) 566 | gf2_add aghb_state mt_const 567 | gf2_rshift aghb_old_state 8 568 | done 569 | 570 | for i in {1..15}; do 571 | local aghb_low=${aghb_state[0]:-0} 572 | local red_val=${AES_GCM_REDUCTION_TABLE[aghb_low & 0xFF]} 573 | local red_int=(0 0 0 $((red_val << 16))) 574 | gf2_rshift aghb_state 8 575 | gf2_add aghb_state red_int 576 | done 577 | } 578 | 579 | ######################################## 580 | ## HMAC+PRF 581 | ######################################## 582 | 583 | hmac_sha256_digest() { 584 | local key=$1 msg=$2 585 | if (( $(hex_len "$key") > 64 )); then 586 | key=$((sha256_digest "$key")) 587 | fi 588 | while (( $(hex_len "$key") < 64 )); do 589 | key+=00 590 | done 591 | local ikey=($(gf2_from_hex $key)) 592 | local ipad=() 593 | while (( ${#ipad[@]} < 16 )); do ipad+=(0x36363636); done 594 | gf2_add ikey ipad 0 595 | local okey=($(gf2_from_hex $key)) 596 | local opad=() 597 | while (( ${#opad[@]} < 16 )); do opad+=(0x5C5C5C5C); done 598 | gf2_add okey opad 0 599 | 600 | local inner_digest=$(sha256_digest $(gf2_to_hex ikey)${msg}) 601 | local outer_digest=$(sha256_digest $(gf2_to_hex okey)${inner_digest}) 602 | echo "$outer_digest" 603 | } 604 | 605 | hmac_sha256_prf() { 606 | local secret=$1 label=$2 seed=$3 length=$4 607 | label=$(hex_convert_from_string "$label") 608 | seed=${label}${seed} 609 | local A=${seed} 610 | local result= 611 | while (( $(hex_len "$result") < length )); do 612 | A=$(hmac_sha256_digest "$secret" "${A}") 613 | result+=$(hmac_sha256_digest "$secret" "${A}${seed}") 614 | done 615 | hex_slice "$result" 0 $length 616 | } 617 | 618 | ######################################## 619 | ## TLS 620 | ######################################## 621 | 622 | TLS_RECORD_MAX_SIZE=16384 623 | RSA_BC_POW_PRGM='r=1;while(e){if(e%2)r=(r*b)%m;b=(b*b)%m;e/=2};r' 624 | 625 | tls_all_handshakes= 626 | tls_handshake_buffer= 627 | tls_changecipherspec_buffer= 628 | tls_appdata_buffer= 629 | 630 | tls_write_gcm= 631 | tls_write_encrypted=0 632 | tls_write_seq=0 633 | 634 | tls_read_gcm= 635 | tls_read_encrypted=0 636 | tls_read_seq=0 637 | 638 | tls_send_record() { 639 | local type=$1 fragment=$2 640 | local i=0 j=$(hex_len "$fragment") 641 | while (( i < j )); do 642 | local chunk=$(hex_slice_lenient "$fragment" $i $TLS_RECORD_MAX_SIZE) 643 | if (( $tls_write_encrypted )); then 644 | local nonce=$(printf %016X $tls_write_seq) 645 | local header=${nonce}${type}0303$(printf %04X $(hex_len $chunk)) 646 | chunk=${nonce}$(aes_gcm_do_encrypt tls_write_gcm $nonce $header $chunk) 647 | (( tls_write_seq += 1 )) 648 | fi 649 | write_to_fd $sockfd ${type}0303$(printf %04X $(hex_len $chunk))${chunk} 650 | (( i += TLS_RECORD_MAX_SIZE )) 651 | done 652 | } 653 | 654 | tls_process_record() { 655 | local type version dlen length fragment 656 | type=$(read_from_fd $sockfd 1) 657 | version=$(read_from_fd $sockfd 2) 658 | dlen=$(read_from_fd $sockfd 2) 659 | length=$(hex_int $dlen) 660 | fragment=$(read_from_fd $sockfd $length) 661 | 662 | if (( $tls_read_encrypted )); then 663 | # decrypted length is 24 bytes shorter (8 from nonce, 16 from GCM tag) 664 | local nonce 665 | local header=$(printf %016X $tls_read_seq)${type}${version}$(printf %04X $((length - 24))) 666 | hex_consume nonce fragment 8 667 | fragment=$(aes_gcm_do_decrypt tls_read_gcm $nonce $header $fragment) 668 | (( tls_read_seq += 1 )) 669 | fi 670 | 671 | case $type in 672 | 14) # change cipher spec 673 | tls_changecipherspec_buffer+=$fragment 674 | ;; 675 | 15) # alert 676 | local value=$(hex_int $fragment) 677 | die "SSL alert level $((value >> 8)) description $((value & 0xFF))" 678 | ;; 679 | 16) # handshake 680 | tls_handshake_buffer+=$fragment 681 | ;; 682 | 17) # application data 683 | tls_appdata_buffer+=$fragment 684 | ;; 685 | *) 686 | die 'unknown record type' 687 | ;; 688 | esac 689 | } 690 | 691 | tls_send_handshake() { 692 | local type=$1 msg=$2 693 | local fragment=${type}$(printf %06X $(hex_len $msg))${msg} 694 | tls_all_handshakes+=$fragment 695 | tls_send_record 16 $fragment 696 | } 697 | 698 | tls_recv_handshake() { 699 | local expected_type=$1 700 | local -n _tls_rh_msg=$2 701 | while true; do 702 | if (( $(hex_len "$tls_handshake_buffer") >= 4 )); then 703 | local type=$(hex_slice $tls_handshake_buffer 0 1) 704 | local dlen=$(hex_slice $tls_handshake_buffer 1 3) 705 | local length=$(hex_int $dlen) 706 | if [ $type != $expected_type ]; then 707 | die 'unexpected handshake message' 708 | fi 709 | if (( $(hex_len $tls_handshake_buffer) >= 4 + length )); then 710 | hex_consume _tls_rh_msg tls_handshake_buffer 4 711 | hex_consume _tls_rh_msg tls_handshake_buffer $length 712 | tls_all_handshakes+=${type}${dlen}${_tls_rh_msg} 713 | return 714 | fi 715 | fi 716 | tls_process_record 717 | done 718 | } 719 | 720 | tls_do_handshake() { 721 | local hostname=$1 722 | local tmp_dlen 723 | 724 | ##### ClientHello 725 | local sni_ext=00$(printf %04X ${#hostname})$(hex_convert_from_string "$hostname") 726 | sni_ext=$(printf %04X $(hex_len $sni_ext))${sni_ext} 727 | sni_ext=0000$(printf %04X $(hex_len $sni_ext))${sni_ext} 728 | local all_exts=${sni_ext} 729 | local cipher_suite=009C 730 | local client_random=$(read_from_fd $randfd 32) 731 | local client_hello=0303 # protocol version 732 | client_hello+=$client_random # client random 733 | client_hello+=00 # session ID 734 | client_hello+=0002${cipher_suite} # cipher suite list 735 | client_hello+=0100 # compresion methods 736 | client_hello+=$(printf %04X $(hex_len $all_exts))${all_exts} 737 | tls_send_handshake 01 $client_hello 738 | 739 | ##### ServerHello 740 | local server_hello 741 | tls_recv_handshake 02 server_hello 742 | local server_random=$(hex_slice "$server_hello" 2 32) 743 | 744 | ##### Certificate 745 | local certificate 746 | tls_recv_handshake 0B certificate 747 | # get certificate set 748 | hex_consume tmp_dlen certificate 3 749 | certificate=$(hex_slice "$certificate" 0 $(hex_int $tmp_dlen)) 750 | # get first certificate 751 | hex_consume tmp_dlen certificate 3 752 | certificate=$(hex_slice "$certificate" 0 $(hex_int $tmp_dlen)) 753 | 754 | ##### ServerHelloDone 755 | local server_hello_done 756 | tls_recv_handshake 0E server_hello_done 757 | 758 | ##### extract public key from certificate 759 | local pk_m pk_e 760 | certificate=$(asn1_enter "$certificate") 761 | certificate=$(asn1_enter "$certificate") 762 | if (( $(hex_int $(hex_slice "$certificate" 0 1)) & 0xC0 )); then 763 | # skip version tag 764 | certificate=$(asn1_skip "$certificate") 765 | fi 766 | certificate=$(asn1_skip "$certificate") 767 | certificate=$(asn1_skip "$certificate") 768 | certificate=$(asn1_skip "$certificate") 769 | certificate=$(asn1_skip "$certificate") 770 | certificate=$(asn1_skip "$certificate") 771 | certificate=$(asn1_enter "$certificate") 772 | local algo=$(asn1_enter "$certificate") 773 | algo=$(asn1_enter "$algo") 774 | if [ "$algo" != 2A864886F70D010101 ]; then 775 | die 'not RSA certificate' 776 | fi 777 | certificate=$(asn1_skip "$certificate") 778 | certificate=$(asn1_enter "$certificate") 779 | if [ $(hex_slice "$certificate" 0 1) != 00 ]; then 780 | die 'bad key padding' 781 | fi 782 | certificate=$(hex_slice "$certificate" 1) 783 | certificate=$(asn1_enter "$certificate") 784 | pk_m=$(hex_strip_leading_zeroes $(asn1_enter "$certificate")) 785 | certificate=$(asn1_skip "$certificate") 786 | pk_e=$(hex_strip_leading_zeroes $(asn1_enter "$certificate")) 787 | if [ -z "$pk_m" ] || [ -z "$pk_e" ]; then 788 | die 'empty public key' 789 | fi 790 | 791 | ##### generate premaster secret 792 | local pre_master_secret=0303$(read_from_fd $randfd 46) 793 | 794 | ##### encrypt premaster secret 795 | local padded_pms=00${pre_master_secret} 796 | while (( $(hex_len $padded_pms) < $(hex_len $pk_m) - 2 )); do 797 | padded_pms=AA${padded_pms} 798 | done 799 | padded_pms=0002${padded_pms} 800 | local encr_pms=$(BC_LINE_LENGTH=9999 bc \ 801 | <<<"obase=16;ibase=16;b=${padded_pms};e=${pk_e};m=${pk_m};${RSA_BC_POW_PRGM}") 802 | while (( ${#encr_pms} < ${#pk_m} )); do encr_pms=0${encr_pms}; done 803 | 804 | # ClientKeyExchange 805 | local client_kex=$(printf %04X $(hex_len $encr_pms))${encr_pms} 806 | tls_send_handshake 10 $client_kex 807 | 808 | ##### calculate master secret 809 | local master_secret=$(hmac_sha256_prf $pre_master_secret 'master secret' \ 810 | ${client_random}${server_random} 48) 811 | local key_block=$(hmac_sha256_prf $master_secret 'key expansion' \ 812 | ${server_random}${client_random} 40) 813 | 814 | # Client ChangeCipherSpec 815 | tls_send_record 14 01 816 | aes_gcm_init_state tls_write_gcm $(hex_slice $key_block 0 16) $(hex_slice $key_block 32 4) 817 | tls_write_encrypted=1 818 | 819 | # Client Finished 820 | local verify_data=$(hmac_sha256_prf $master_secret 'client finished' \ 821 | $(sha256_digest $tls_all_handshakes) 12) 822 | tls_send_handshake 14 $verify_data 823 | 824 | # Server ChangeCipherSpec 825 | while [ -z "$tls_changecipherspec_buffer" ]; do 826 | tls_process_record 827 | done 828 | if [ "$tls_changecipherspec_buffer" != 01 ]; then 829 | die 'bad CCS msg' 830 | fi 831 | aes_gcm_init_state tls_read_gcm $(hex_slice $key_block 16 16) $(hex_slice $key_block 36 4) 832 | tls_read_encrypted=1 833 | 834 | # Server Finished 835 | local verify_data=$(hmac_sha256_prf $master_secret 'server finished' \ 836 | $(sha256_digest $tls_all_handshakes) 12) 837 | local server_finished 838 | tls_recv_handshake 14 server_finished 839 | if [ "$server_finished" != "$verify_data" ]; then 840 | die 'bad handshake hash' 841 | fi 842 | } 843 | 844 | tls_send_data() { 845 | local data=$1 846 | tls_send_record 17 $1 847 | } 848 | 849 | tls_recv_data() { 850 | local -n _tls_recv_data_buf=$1 851 | while [ -z "$tls_appdata_buffer" ]; do 852 | tls_process_record 853 | done 854 | _tls_recv_data_buf=$tls_appdata_buffer 855 | tls_appdata_buffer= 856 | } 857 | 858 | main() { 859 | if [ -n "${1:-}" ]; then 860 | local url=$1 861 | else 862 | local url=https://www.google.com/robots.txt 863 | fi 864 | url=${url#https://} 865 | hostname=${url%%/*} 866 | path=/${url#*/} 867 | if [ "$hostname" = "$path" ]; then path=/; fi 868 | 869 | exec {sockfd}<>/dev/tcp/"$hostname"/443 870 | exec {randfd}/dev/null 872 | 873 | local get_req="GET ${path} HTTP/1.1"$'\r\n' 874 | get_req+="Host: ${hostname}"$'\r\n' 875 | get_req+="Connection: close"$'\r\n\r\n' 876 | 877 | echo 'running handshake ...' >&2 878 | tls_do_handshake "$hostname" 879 | echo 'sending request ...' >&2 880 | tls_send_data $(hex_convert_from_string "$get_req") 881 | echo 'receiving response ...' >&2 882 | while true; do 883 | local data 884 | tls_recv_data data 885 | write_to_fd 1 $data 886 | done 887 | } 888 | 889 | main "$@" 890 | --------------------------------------------------------------------------------