1#
2# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License").
5# You may not use this file except in compliance with the License.
6# A copy of the License is located at
7#
8#  http://aws.amazon.com/apache2.0
9#
10# or in the "license" file accompanying this file. This file is distributed
11# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12# express or implied. See the License for the specific language governing
13# permissions and limitations under the License.
14#
15import argparse
16import os
17import sys
18import subprocess
19import itertools
20import multiprocessing
21import json
22import time
23from pprint import pprint
24from os import environ
25from multiprocessing.pool import ThreadPool
26from s2n_test_constants import *
27
28
29def cleanup_processes(*processes):
30    for p in processes:
31        p.kill()
32        p.wait()
33
34def run_sslyze_scan(endpoint, port, scan_output_location, enter_fips_mode=False):
35    """
36    Run SSLyze scan against s2nd listening on `endpoint` and `port`
37
38    :param int endpoint: endpoint for s2nd to listen on
39    :param int port: port for s2nd to listen on
40    :param str scan_output_location: Path and Filename of where to output JSON Results file
41    :param bool enter_fips_mode: True if s2nd should enter libcrypto's FIPS mode. Libcrypto must be built with a FIPS module to enter FIPS mode.
42    :return: 0 on successfully negotiation(s), -1 on failure
43    """
44
45    s2nd_cmd = ["../../bin/s2nd"]
46    s2nd_cmd.extend([str(endpoint), str(port), "-n", "-s", "--parallelize"])
47
48    s2nd_ciphers = "test_all_tls12"
49    if enter_fips_mode == True:
50        s2nd_ciphers = "test_all_fips"
51        s2nd_cmd.append("--enter-fips-mode")
52    s2nd_cmd.append("--ciphers")
53    s2nd_cmd.append(s2nd_ciphers)
54
55    # Run s2nd in the background
56    s2nd = subprocess.Popen(s2nd_cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
57
58
59    sslyze_cmd = ["sslyze"]
60    sslyze_cmd.extend(["--robot", str(str(endpoint) + ":" + str(port)), str("--json_out=" + scan_output_location)])
61
62    sslyze = subprocess.Popen(sslyze_cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
63    sslyze.wait(timeout=(300 * 1000))
64
65    #cleanup
66    cleanup_processes(s2nd, sslyze)
67
68    return 0
69
70def print_result(result_prefix, return_code):
71    suffix = ""
72    if return_code == 0:
73        if sys.stdout.isatty():
74            suffix = "\033[32;1mPASSED\033[0m"
75        else:
76            suffix = "PASSED"
77    else:
78        if sys.stdout.isatty():
79            suffix = "\033[31;1mFAILED\033[0m"
80        else:
81            suffix ="FAILED"
82
83    print(result_prefix + suffix)
84
85def check_sslyze_results(scan_output_location):
86    json_obj = json.load(open(scan_output_location))
87    scan_time = json_obj["total_scan_time"]
88    robot_result = json_obj["accepted_targets"][0]["commands_results"]["robot"]
89
90    if("error_message" in robot_result):
91        print_result("SSLyze Error: " + robot_result["error_message"] + " ", 1)
92        return 1
93
94    failures = 0
95    robot_attack_failure = 0
96
97    if(robot_result["robot_result_enum"] != "NOT_VULNERABLE_NO_ORACLE"):
98        robot_attack_failure = 1
99        failures += 1
100
101    print_result("ROBOT Attack Regression Test... ", robot_attack_failure)
102
103    print("\nSSLyze Results Location: " + scan_output_location)
104    print("SSLyze Scan Time: %0.2f seconds\n" % float(scan_time))
105
106    return failures
107
108def run_sslyze_test(host, port, fips_mode):
109    seconds_since_epoch = str(int(time.time()))
110    scan_output_location = "/tmp/sslyze_output_%s.json" % seconds_since_epoch
111
112    run_sslyze_scan(host, port, scan_output_location, fips_mode)
113    failed = check_sslyze_results(scan_output_location)
114
115    os.remove(scan_output_location)
116    return failed
117
118
119def main():
120    parser = argparse.ArgumentParser(description='Runs SSLyze scan against s2nd')
121    parser.add_argument('host', help='The host for s2nd to bind to')
122    parser.add_argument('port', type=int, help='The port for s2nd to bind to')
123    parser.add_argument('--libcrypto', default='openssl-1.1.1', choices=S2N_LIBCRYPTO_CHOICES,
124            help="""The Libcrypto that s2n was built with. s2n supports different cipher suites depending on
125                    libcrypto version. Defaults to openssl-1.1.1.""")
126    args = parser.parse_args()
127
128    # Retrieve the test ciphers to use based on the libcrypto version s2n was built with
129    host = args.host
130    port = args.port
131
132    fips_mode = False
133    if environ.get("S2N_TEST_IN_FIPS_MODE") is not None:
134        fips_mode = True
135        print("\n\tRunning s2nd in FIPS mode.")
136
137    print("\n\tRunning SSLyze tests with: " + os.popen('openssl version').read())
138
139    return run_sslyze_test(host, port, fips_mode)
140
141
142if __name__ == "__main__":
143    sys.exit(main())
144