From 3107661b7c4ef3a7f4935320033b38191982becb Mon Sep 17 00:00:00 2001 From: Richard Soderberg Date: Fri, 18 Sep 2015 14:21:38 -0700 Subject: [PATCH 1/8] Unroll the if-return/elif-return/else-return chain in test_cipher_on_target. Rather than doing if-return, elif-return, else-return, just do if-return, if-return, if-return. This provides no immediate benefit to the code itself, but permits the introduction of code that alters the $sigalg variable in between the first if-return and the latter two in an upcoming commit. --- cipherscan | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/cipherscan b/cipherscan index 24be563..30c8357 100755 --- a/cipherscan +++ b/cipherscan @@ -597,34 +597,34 @@ test_cipher_on_target() { verbose "handshake failed, no ciphersuite was returned" result='ConnectionFailure' return 2 + fi # if cipher contains NONE, the cipher wasn't accepted - elif [[ "$cipher" == '(NONE) ' ]]; then + if [[ "$cipher" == '(NONE) ' ]]; then result="$cipher $protocols $pubkey $sigalg $trusted $tickethint $ocspstaple $pfs $current_curves $curves_ordering" verbose "handshake failed, server returned ciphersuite '$result'" return 1 + fi # the connection succeeded - else - current_curves="None" - # if pfs uses ECDH, test supported curves - if [[ $pfs =~ ECDH ]]; then - has_curves="True" - if [[ $TEST_CURVES == "True" ]]; then - test_curves - if [[ -n $ecc_ciphers ]]; then - ecc_ciphers+=":" - fi - ecc_ciphers+="$cipher" - else - # resolve the openssl curve to the proper IANA name - current_curves="$(get_curve_name "$(echo $pfs|cut -d ',' -f2)")" + current_curves="None" + # if pfs uses ECDH, test supported curves + if [[ $pfs =~ ECDH ]]; then + has_curves="True" + if [[ $TEST_CURVES == "True" ]]; then + test_curves + if [[ -n $ecc_ciphers ]]; then + ecc_ciphers+=":" fi + ecc_ciphers+="$cipher" + else + # resolve the openssl curve to the proper IANA name + current_curves="$(get_curve_name "$(echo $pfs|cut -d ',' -f2)")" fi - result="$cipher $protocols $pubkey $sigalg $trusted $tickethint $ocspstaple $pfs $current_curves $curves_ordering" - verbose "handshake succeeded, server returned ciphersuite '$result'" - return 0 fi + result="$cipher $protocols $pubkey $sigalg $trusted $tickethint $ocspstaple $pfs $current_curves $curves_ordering" + verbose "handshake succeeded, server returned ciphersuite '$result'" + return 0 } # Calculate the average handshake time for a specific ciphersuite From 1828183e3f396a2d7ec276e90b1648907a5837a5 Mon Sep 17 00:00:00 2001 From: Richard Soderberg Date: Fri, 18 Sep 2015 14:56:32 -0700 Subject: [PATCH 2/8] Extract the list of TLS versions to test into an array. --- cipherscan | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cipherscan b/cipherscan index 30c8357..d8bc02e 100755 --- a/cipherscan +++ b/cipherscan @@ -456,6 +456,14 @@ parse_openssl_output() { fi } +TLS_VERSIONS_TO_TEST=( + '-ssl2' + '-ssl3' + '-tls1' + '-tls1_1' + '-tls1_2' +) + # Connect to a target host with the selected ciphersuite test_cipher_on_target() { local sslcommand="$*" @@ -465,8 +473,7 @@ test_cipher_on_target() { pfs="" previous_cipher="" certificates="" - for tls_version in "-ssl2" "-ssl3" "-tls1" "-tls1_1" "-tls1_2" - do + for tls_version in "${TLS_VERSIONS_TO_TEST[@]}"; do # sslv2 client hello doesn't support SNI extension # in SSLv3 mode OpenSSL just ignores the setting so it's ok # -status exception is ignored in SSLv2, go figure From 32bf52a452d36f069c6aef63e23747d68770b6ef Mon Sep 17 00:00:00 2001 From: Richard Soderberg Date: Fri, 18 Sep 2015 14:59:30 -0700 Subject: [PATCH 3/8] Store the found protocols in an array, rather than a CSV-joined string. --- cipherscan | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/cipherscan b/cipherscan index d8bc02e..90d685a 100755 --- a/cipherscan +++ b/cipherscan @@ -469,7 +469,7 @@ test_cipher_on_target() { local sslcommand="$*" cipher="" local cmnd="" - protocols="" + protocols=() pfs="" previous_cipher="" certificates="" @@ -577,16 +577,12 @@ test_cipher_on_target() { fi # handling of TLSv1.2 only cipher suites if [[ ! -z "$previous_cipher" ]] && [[ "$previous_cipher" != "$current_cipher" ]] && [[ "$current_cipher" != "0000" ]]; then - unset protocols + protocols=() fi previous_cipher=$current_cipher # connection succeeded, add TLS version to positive results - if [[ -z "$protocols" ]]; then - protocols=$current_protocol - else - protocols="$protocols,$current_protocol" - fi + protocols+=("$current_protocol") cipher=$current_cipher pfs=$current_pfs [[ -z $pfs ]] && pfs="None" @@ -606,9 +602,13 @@ test_cipher_on_target() { return 2 fi + # Pre-join this, since we use it in a couple of places below. + join_array_by_char ',' "${protocols[@]}" + protocols_csv="$joined_array" + # if cipher contains NONE, the cipher wasn't accepted if [[ "$cipher" == '(NONE) ' ]]; then - result="$cipher $protocols $pubkey $sigalg $trusted $tickethint $ocspstaple $pfs $current_curves $curves_ordering" + result="$cipher $protocols_csv $pubkey $sigalg $trusted $tickethint $ocspstaple $pfs $current_curves $curves_ordering" verbose "handshake failed, server returned ciphersuite '$result'" return 1 fi @@ -629,7 +629,7 @@ test_cipher_on_target() { current_curves="$(get_curve_name "$(echo $pfs|cut -d ',' -f2)")" fi fi - result="$cipher $protocols $pubkey $sigalg $trusted $tickethint $ocspstaple $pfs $current_curves $curves_ordering" + result="$cipher $protocols_csv $pubkey $sigalg $trusted $tickethint $ocspstaple $pfs $current_curves $curves_ordering" verbose "handshake succeeded, server returned ciphersuite '$result'" return 0 } From 0be95b821a8721061a64470b0b938a3e0fa71b9e Mon Sep 17 00:00:00 2001 From: Richard Soderberg Date: Fri, 18 Sep 2015 15:39:07 -0700 Subject: [PATCH 4/8] Emit an array of certificate signature algorithms, where applicable. Certain SSL servers may emit a different certificate for each TLS protocol version. Previously, we simply emitted one of their signature algorithms. Now, we emit an array where each element corresponds to the array of TLS versions. This will be extended to the other certificate-dependent attributes in future commits. --- cipherscan | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/cipherscan b/cipherscan index 90d685a..c76e9fd 100755 --- a/cipherscan +++ b/cipherscan @@ -456,6 +456,43 @@ parse_openssl_output() { fi } +flatten_or_join_array_by_char() { + # Two or less parameters (join + 0 or 1 value), then no need to join; return the string. + if (( $# <= 2 )); then + joined_array="$2" + return + fi + # Discard the join string (usually ':', could be others). + local join_by="$1" + shift + + local found_many='' + local last_value='' + for each_value in "$@"; do + if [[ -z $last_value ]]; then + # This is the first one, so store it. + last_value="$each_value" + continue + fi + if [[ $last_value != "$each_value" ]]; then + # This one is different, so we found many. Stop checking. + found_many=1 + break + fi + done + + if [[ -z $found_many ]]; then + # We only found one, so emit that. + joined_array="$1" + return + else + # We found many, so join them by whatever. + join_array_by_char "$join_by" "$@" + # joined_array is now set. All done. + return + fi +} + TLS_VERSIONS_TO_TEST=( '-ssl2' '-ssl3' @@ -470,9 +507,11 @@ test_cipher_on_target() { cipher="" local cmnd="" protocols=() + versions=() pfs="" previous_cipher="" certificates="" + declare -A sigalgs=() for tls_version in "${TLS_VERSIONS_TO_TEST[@]}"; do # sslv2 client hello doesn't support SNI extension # in SSLv3 mode OpenSSL just ignores the setting so it's ok @@ -587,7 +626,7 @@ test_cipher_on_target() { pfs=$current_pfs [[ -z $pfs ]] && pfs="None" pubkey=$current_pubkey - sigalg=$current_sigalg + sigalgs[$current_protocol]="$current_sigalg" trusted=$current_trusted tickethint=$current_tickethint ocspstaple=$current_ocspstaple @@ -602,6 +641,24 @@ test_cipher_on_target() { return 2 fi + # Flatten the sigalgs list to a single item if every entry is the same. + if (( ${#sigalgs[*]} > 1 )); then + local sigalgs_values=() + for each_protocol in "${protocols[@]}"; do + sigalgs_values+=("${sigalgs[$each_protocol]}") + done + if [[ $OUTPUTFORMAT == 'json' ]]; then + # Don't deduplicate for JSON. + join_array_by_char ',' "${sigalgs_values[@]}" + else + flatten_or_join_array_by_char ',' "${sigalgs_values[@]}" + fi + sigalg="$joined_array" + else + # Just extract the one value that's present and use it. + sigalg="${sigalgs[@]}" + fi + # Pre-join this, since we use it in a couple of places below. join_array_by_char ',' "${protocols[@]}" protocols_csv="$joined_array" From 638e0cbd10d1633a5c2f2dce62bbf3a9c464248d Mon Sep 17 00:00:00 2001 From: Richard Soderberg Date: Fri, 18 Sep 2015 16:12:21 -0700 Subject: [PATCH 5/8] Add handling for TLS-variant results for the PFS value. As before with signature algorithms, we need to handle the case where the PFS value varies based on SSL protocol version. --- cipherscan | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/cipherscan b/cipherscan index c76e9fd..5aef14a 100755 --- a/cipherscan +++ b/cipherscan @@ -508,10 +508,10 @@ test_cipher_on_target() { local cmnd="" protocols=() versions=() - pfs="" previous_cipher="" certificates="" declare -A sigalgs=() + declare -A pfses=() for tls_version in "${TLS_VERSIONS_TO_TEST[@]}"; do # sslv2 client hello doesn't support SNI extension # in SSLv3 mode OpenSSL just ignores the setting so it's ok @@ -623,8 +623,8 @@ test_cipher_on_target() { # connection succeeded, add TLS version to positive results protocols+=("$current_protocol") cipher=$current_cipher - pfs=$current_pfs - [[ -z $pfs ]] && pfs="None" + [[ -z $current_pfs ]] && current_pfs="None" + pfses[$current_protocol]="$current_pfs" pubkey=$current_pubkey sigalgs[$current_protocol]="$current_sigalg" trusted=$current_trusted @@ -659,6 +659,24 @@ test_cipher_on_target() { sigalg="${sigalgs[@]}" fi + # Flatten the pfses list to a single item if every entry is the same. + if (( ${#pfses[*]} > 1 )); then + local pfses_values=() + for each_protocol in "${protocols[@]}"; do + pfses_values+=("${pfses[$each_protocol]}") + done + if [[ $OUTPUTFORMAT == 'json' ]]; then + # Don't deduplicate for JSON. + join_array_by_char ';' "${pfses_values[@]}" + else + flatten_or_join_array_by_char ';' "${pfses_values[@]}" + fi + pfs="$joined_array" + else + # Just extract the one value that's present and use it. + pfs="${pfses[@]}" + fi + # Pre-join this, since we use it in a couple of places below. join_array_by_char ',' "${protocols[@]}" protocols_csv="$joined_array" @@ -993,9 +1011,7 @@ display_results_in_json() { fi echo -n "\"ticket_hint\":\"${cipher_arr[5]}\"," echo -n "\"ocsp_stapling\":\"${cipher_arr[6]}\"," - pfs="${cipher_arr[7]}" - [[ -z $pfs ]] && pfs="None" - echo -n "\"pfs\":\"$pfs\"" + echo -n "\"pfs\":[\"${cipher_arr[7]//\;/\",\"}\"]" if [[ "${cipher_arr[0]}" =~ ECDH ]]; then echo -n "," echo -n "\"curves\":[\"${cipher_arr[8]//,/\",\"}\"]" From eb752c541c7e5836401150ea367bb3f96e35a887 Mon Sep 17 00:00:00 2001 From: Richard Soderberg Date: Fri, 18 Sep 2015 16:29:32 -0700 Subject: [PATCH 6/8] Add handling for TLS-variant ticket hint value. This, as previous commits, adds support for reporting the TLS ticket hint value per-protocol, which results in a lot of 'None' for SSLv3 (as expected). --- cipherscan | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/cipherscan b/cipherscan index 5aef14a..770dd3d 100755 --- a/cipherscan +++ b/cipherscan @@ -512,6 +512,7 @@ test_cipher_on_target() { certificates="" declare -A sigalgs=() declare -A pfses=() + declare -A tickethints=() for tls_version in "${TLS_VERSIONS_TO_TEST[@]}"; do # sslv2 client hello doesn't support SNI extension # in SSLv3 mode OpenSSL just ignores the setting so it's ok @@ -628,7 +629,7 @@ test_cipher_on_target() { pubkey=$current_pubkey sigalgs[$current_protocol]="$current_sigalg" trusted=$current_trusted - tickethint=$current_tickethint + tickethints[$current_protocol]=$current_tickethint ocspstaple=$current_ocspstaple certificates="$current_certificates" # grab the cipher and PFS key size @@ -677,6 +678,24 @@ test_cipher_on_target() { pfs="${pfses[@]}" fi + # Flatten the tickethints list to a single item if every entry is the same. + if (( ${#tickethints[*]} > 1 )); then + local tickethints_values=() + for each_protocol in "${protocols[@]}"; do + tickethints_values+=("${tickethints[$each_protocol]}") + done + if [[ $OUTPUTFORMAT == 'json' ]]; then + # Don't deduplicate for JSON. + join_array_by_char ',' "${tickethints_values[@]}" + else + flatten_or_join_array_by_char ',' "${tickethints_values[@]}" + fi + tickethint="$joined_array" + else + # Just extract the one value that's present and use it. + tickethint="${tickethints[@]}" + fi + # Pre-join this, since we use it in a couple of places below. join_array_by_char ',' "${protocols[@]}" protocols_csv="$joined_array" @@ -1009,7 +1028,7 @@ display_results_in_json() { if [[ -n $CAPATH ]]; then echo -n "\"certificates\":[${ciphercertificates[$ctr]}]," fi - echo -n "\"ticket_hint\":\"${cipher_arr[5]}\"," + echo -n "\"ticket_hint\":[\"${cipher_arr[5]//,/\",\"}\"]," echo -n "\"ocsp_stapling\":\"${cipher_arr[6]}\"," echo -n "\"pfs\":[\"${cipher_arr[7]//\;/\",\"}\"]" if [[ "${cipher_arr[0]}" =~ ECDH ]]; then From 8757bbd039c34693ea6c65cf47c8196c8ce73435 Mon Sep 17 00:00:00 2001 From: Richard Soderberg Date: Fri, 18 Sep 2015 16:36:03 -0700 Subject: [PATCH 7/8] Add handling for TLS-dependent trusted values. As per previous commits, this adds TLS-dependent support for the 'Trusted' value in the output. --- cipherscan | 46 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/cipherscan b/cipherscan index 770dd3d..b2b99dc 100755 --- a/cipherscan +++ b/cipherscan @@ -513,6 +513,8 @@ test_cipher_on_target() { declare -A sigalgs=() declare -A pfses=() declare -A tickethints=() + declare -A ocspstaples=() + declare -A trusteds=() for tls_version in "${TLS_VERSIONS_TO_TEST[@]}"; do # sslv2 client hello doesn't support SNI extension # in SSLv3 mode OpenSSL just ignores the setting so it's ok @@ -628,9 +630,9 @@ test_cipher_on_target() { pfses[$current_protocol]="$current_pfs" pubkey=$current_pubkey sigalgs[$current_protocol]="$current_sigalg" - trusted=$current_trusted + trusteds[$current_protocol]=$current_trusted tickethints[$current_protocol]=$current_tickethint - ocspstaple=$current_ocspstaple + ocspstaples[$current_protocol]=$current_ocspstaple certificates="$current_certificates" # grab the cipher and PFS key size done @@ -696,6 +698,42 @@ test_cipher_on_target() { tickethint="${tickethints[@]}" fi + # Flatten the ocspstaples list to a single item if every entry is the same. + if (( ${#ocspstaples[*]} > 1 )); then + local ocspstaples_values=() + for each_protocol in "${protocols[@]}"; do + ocspstaples_values+=("${ocspstaples[$each_protocol]}") + done + if [[ $OUTPUTFORMAT == 'json' ]]; then + # Don't deduplicate for JSON. + join_array_by_char ',' "${ocspstaples_values[@]}" + else + flatten_or_join_array_by_char ',' "${ocspstaples_values[@]}" + fi + ocspstaple="$joined_array" + else + # Just extract the one value that's present and use it. + ocspstaple="${ocspstaples[@]}" + fi + + # Flatten the trusteds list to a single item if every entry is the same. + if (( ${#trusteds[*]} > 1 )); then + local trusteds_values=() + for each_protocol in "${protocols[@]}"; do + trusteds_values+=("${trusteds[$each_protocol]}") + done + if [[ $OUTPUTFORMAT == 'json' ]]; then + # Don't deduplicate for JSON. + join_array_by_char ',' "${trusteds_values[@]}" + else + flatten_or_join_array_by_char ',' "${trusteds_values[@]}" + fi + trusted="$joined_array" + else + # Just extract the one value that's present and use it. + trusted="${trusteds[@]}" + fi + # Pre-join this, since we use it in a couple of places below. join_array_by_char ',' "${protocols[@]}" protocols_csv="$joined_array" @@ -1024,12 +1062,12 @@ display_results_in_json() { echo -n "\"protocols\":[\"${cipher_arr[1]//,/\",\"}\"]," echo -n "\"pubkey\":[\"${cipher_arr[2]//,/\",\"}\"]," echo -n "\"sigalg\":[\"${cipher_arr[3]//,/\",\"}\"]," - echo -n "\"trusted\":\"${cipher_arr[4]//,/\",\"}\"," + echo -n "\"trusted\":[\"${cipher_arr[4]//,/\",\"}\"]," if [[ -n $CAPATH ]]; then echo -n "\"certificates\":[${ciphercertificates[$ctr]}]," fi echo -n "\"ticket_hint\":[\"${cipher_arr[5]//,/\",\"}\"]," - echo -n "\"ocsp_stapling\":\"${cipher_arr[6]}\"," + echo -n "\"ocsp_stapling\":[\"${cipher_arr[6]//,/\",\"}\"]," echo -n "\"pfs\":[\"${cipher_arr[7]//\;/\",\"}\"]" if [[ "${cipher_arr[0]}" =~ ECDH ]]; then echo -n "," From d7a745866771ec6dd8035a238657e74e57330e4c Mon Sep 17 00:00:00 2001 From: Richard Soderberg Date: Fri, 18 Sep 2015 16:38:41 -0700 Subject: [PATCH 8/8] Add handling of TLS-dependent pubkey sizes. As with previous commits, this adds reporting for TLS-dependent pubkey sizes. --- cipherscan | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/cipherscan b/cipherscan index b2b99dc..fe289f9 100755 --- a/cipherscan +++ b/cipherscan @@ -515,6 +515,7 @@ test_cipher_on_target() { declare -A tickethints=() declare -A ocspstaples=() declare -A trusteds=() + declare -A pubkeys=() for tls_version in "${TLS_VERSIONS_TO_TEST[@]}"; do # sslv2 client hello doesn't support SNI extension # in SSLv3 mode OpenSSL just ignores the setting so it's ok @@ -628,7 +629,7 @@ test_cipher_on_target() { cipher=$current_cipher [[ -z $current_pfs ]] && current_pfs="None" pfses[$current_protocol]="$current_pfs" - pubkey=$current_pubkey + pubkeys[$current_protocol]="$current_pubkey" sigalgs[$current_protocol]="$current_sigalg" trusteds[$current_protocol]=$current_trusted tickethints[$current_protocol]=$current_tickethint @@ -734,6 +735,24 @@ test_cipher_on_target() { trusted="${trusteds[@]}" fi + # Flatten the pubkeys list to a single item if every entry is the same. + if (( ${#pubkeys[*]} > 1 )); then + local pubkeys_values=() + for each_protocol in "${protocols[@]}"; do + pubkeys_values+=("${pubkeys[$each_protocol]}") + done + if [[ $OUTPUTFORMAT == 'json' ]]; then + # Don't deduplicate for JSON. + join_array_by_char ',' "${pubkeys_values[@]}" + else + flatten_or_join_array_by_char ',' "${pubkeys_values[@]}" + fi + pubkey="$joined_array" + else + # Just extract the one value that's present and use it. + pubkey="${pubkeys[@]}" + fi + # Pre-join this, since we use it in a couple of places below. join_array_by_char ',' "${protocols[@]}" protocols_csv="$joined_array"