1#!/usr/bin/python 2 3""" 4(C) 2018,2019 Jack Lloyd 5 6Botan is released under the Simplified BSD License (see license.txt) 7""" 8 9import subprocess 10import sys 11import os 12import logging 13import optparse # pylint: disable=deprecated-module 14import time 15import shutil 16import tempfile 17import re 18import random 19import json 20import binascii 21import multiprocessing 22from multiprocessing.pool import ThreadPool 23 24# pylint: disable=global-statement,unused-argument 25 26CLI_PATH = None 27TESTS_RUN = 0 28TESTS_FAILED = 0 29 30class TestLogHandler(logging.StreamHandler, object): 31 def emit(self, record): 32 # Do the default stuff first 33 super(TestLogHandler, self).emit(record) 34 if record.levelno >= logging.ERROR: 35 global TESTS_FAILED 36 TESTS_FAILED += 1 37 38def setup_logging(options): 39 if options.verbose: 40 log_level = logging.DEBUG 41 elif options.quiet: 42 log_level = logging.WARNING 43 else: 44 log_level = logging.INFO 45 46 lh = TestLogHandler(sys.stdout) 47 lh.setFormatter(logging.Formatter('%(levelname) 7s: %(message)s')) 48 logging.getLogger().addHandler(lh) 49 logging.getLogger().setLevel(log_level) 50 51def random_port_number(): 52 return random.randint(1024, 65535) 53 54def test_cli(cmd, cmd_options, expected_output=None, cmd_input=None, expected_stderr=None, use_drbg=True): 55 global TESTS_RUN 56 57 TESTS_RUN += 1 58 59 opt_list = [] 60 61 if isinstance(cmd_options, str): 62 opt_list = cmd_options.split(' ') 63 elif isinstance(cmd_options, list): 64 opt_list = cmd_options 65 66 if use_drbg: 67 fixed_drbg_seed = "802" * 32 68 drbg_options = ['--rng-type=drbg', '--drbg-seed=' + fixed_drbg_seed] 69 else: 70 drbg_options = [] 71 72 cmdline = [CLI_PATH, cmd] + drbg_options + opt_list 73 74 logging.debug("Executing '%s'" % (' '.join([CLI_PATH, cmd] + opt_list))) 75 76 stdout = None 77 stderr = None 78 79 if cmd_input is None: 80 proc = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 81 (stdout, stderr) = proc.communicate() 82 else: 83 proc = subprocess.Popen(cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 84 (stdout, stderr) = proc.communicate(cmd_input.encode()) 85 86 if stderr: 87 if expected_stderr is None: 88 logging.error("Got output on stderr %s (stdout was %s)", stderr, stdout) 89 else: 90 if stderr != expected_stderr: 91 logging.error("Got output on stderr %s which did not match expected value %s", stderr, expected_stderr) 92 else: 93 if expected_stderr is not None: 94 logging.error('Expected output on stderr but got nothing') 95 96 output = stdout.decode('ascii').strip() 97 98 if expected_output is not None: 99 if output != expected_output: 100 logging.error("Got unexpected output running cmd %s %s", cmd, cmd_options) 101 logging.info("Output lengths %d vs expected %d", len(output), len(expected_output)) 102 logging.info("Got %s", output) 103 logging.info("Exp %s", expected_output) 104 105 return output 106 107def check_for_command(cmd): 108 cmdline = [CLI_PATH, 'has_command', cmd] 109 proc = subprocess.Popen(cmdline) 110 proc.communicate() 111 112 return proc.returncode == 0 113 114def cli_config_tests(_tmp_dir): 115 prefix = test_cli("config", "prefix") 116 cflags = test_cli("config", "cflags") 117 ldflags = test_cli("config", "ldflags") 118 libs = test_cli("config", "libs") 119 120 if len(prefix) < 4 or prefix[0] != '/': 121 logging.error("Bad prefix %s" % (prefix)) 122 if ("-I%s/include/botan-2" % (prefix)) not in cflags: 123 logging.error("Bad cflags %s" % (cflags)) 124 if not ldflags.endswith(("-L%s/lib" % (prefix))): 125 logging.error("Bad ldflags %s" % (ldflags)) 126 if "-lbotan-2" not in libs: 127 logging.error("Bad libs %s" % (libs)) 128 129def cli_help_tests(_tmp_dir): 130 output = test_cli("help", None, None) 131 132 # Maybe test format somehow?? 133 if len(output) < 500: 134 logging.error("Help output seems very short") 135 136def cli_version_tests(_tmp_dir): 137 output = test_cli("version", None, None) 138 139 version_re = re.compile(r'[0-9]\.[0-9]+\.[0-9]') 140 if not version_re.match(output): 141 logging.error("Unexpected version output %s" % (output)) 142 143 output = test_cli("version", ["--full"], None, None) 144 version_full_re = re.compile(r'Botan [0-9]\.[0-9]+\.[0-9] \(.* revision .*, distribution .*\)$') 145 if not version_full_re.match(output): 146 logging.error("Unexpected version output %s" % (output)) 147 148def cli_is_prime_tests(_tmp_dir): 149 test_cli("is_prime", "5", "5 is probably prime") 150 test_cli("is_prime", "9", "9 is composite") 151 test_cli("is_prime", "548950623407687320763", "548950623407687320763 is probably prime") 152 153def cli_gen_prime_tests(_tmp_dir): 154 test_cli("gen_prime", "64", "15568813029901363163") 155 test_cli("gen_prime", "128", "287193909494025008847286845478788766073") 156 157def cli_cycle_counter(_tmp_dir): 158 output = test_cli("cpu_clock", None, None) 159 160 if output.startswith('No CPU cycle counter on this machine'): 161 return 162 163 have_clock_re = re.compile(r'Estimated CPU clock [0-9\.]+ (M|G)Hz') 164 165 if have_clock_re.match(output): 166 return 167 168 logging.error('Unexpected output from cpu_clock: %s', output) 169 170def cli_entropy_tests(_tmp_dir): 171 output = test_cli("entropy", ["all"], None) 172 173 status_re = re.compile('Polling [a-z0-9_]+ gathered [0-9]+ bytes in [0-9]+ outputs with estimated entropy [0-9]+') 174 unavail_re = re.compile('Source [a-z0-9_]+ is unavailable') 175 comp_re = re.compile('Sample from [a-z0-9_]+ was .* compressed from [0-9]+ bytes to [0-9]+ bytes') 176 output_re = re.compile(r'[A-F0-9]+(...)?') 177 178 status_next = True 179 180 for line in output.split('\n'): 181 if comp_re.match(line): 182 continue 183 184 if status_next: 185 if status_re.match(line) is not None: 186 status_next = False 187 elif unavail_re.match(line) is not None: 188 pass 189 else: 190 logging.error('Unexpected status line %s', line) 191 status_next = False 192 else: 193 if output_re.match(line) is None: 194 logging.error('Unexpected sample line %s', line) 195 status_next = True 196 197def cli_factor_tests(_tmp_dir): 198 test_cli("factor", "97", "97: 97") 199 test_cli("factor", "9753893489562389", "9753893489562389: 21433 455087644733") 200 test_cli("factor", "12019502040659149507", "12019502040659149507: 3298628633 3643787579") 201 202def cli_mod_inverse_tests(_tmp_dir): 203 test_cli("mod_inverse", "97 802", "339") 204 test_cli("mod_inverse", "98 802", "0") 205 206def cli_base64_tests(_tmp_dir): 207 test_cli("base64_enc", "-", "YmVlcyE=", "bees!") 208 test_cli("base64_dec", "-", "bees!", "YmVlcyE=") 209 210def cli_base32_tests(_tmp_dir): 211 test_cli("base32_enc", "-", "MJSWK4ZB", "bees!") 212 test_cli("base32_dec", "-", "bees!", "MJSWK4ZB") 213 214def cli_base58_tests(_tmp_dir): 215 test_cli("base58_enc", "-", "C6sRAr4", "bees!") 216 test_cli("base58_dec", "-", "bees!", "C6sRAr4") 217 218 test_cli("base58_enc", ["--check", "-"], "Cjv15cdjaBc", "F00F") 219 test_cli("base58_dec", ["--check", "-"], "F00F", "Cjv15cdjaBc") 220 221def cli_hex_tests(_tmp_dir): 222 test_cli("hex_enc", "-", "6265657321", "bees!") 223 test_cli("hex_dec", "-", "bees!", "6265657321") 224 225def cli_hash_tests(_tmp_dir): 226 test_cli("hash", "--algo=SHA-256", 227 "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 -", "") 228 229 test_cli("hash", "--algo=SHA-256", 230 "BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD -", "abc") 231 232 test_cli("hash", ["--algo=SHA-256", "--format=base64"], 233 "ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0= -", "abc") 234 235 test_cli("hash", ["--algo=SHA-224", "--format=base58", "--no-fsname"], 236 "MuGc8HkSVyJjfMjPM5UQikPToBTzNucEghcGLe", "abc") 237 238 test_cli("hash", ["--algo=SHA-224", "--format=base58check", "--no-fsname"], 239 "3MmfMqgrhemdVa9bDAGfooukbviWtKMBx2xauL2RsyAe", "abc") 240 241def cli_hmac_tests(tmp_dir): 242 key_file = os.path.join(tmp_dir, 'hmac.key') 243 244 test_cli("rng", ["64", "--output=%s" % (key_file)], "") 245 246 test_cli("hmac", ["--no-fsname", "--hash=SHA-384", key_file, key_file], 247 "E3A8529377030B28A7DBDFC50DDEC8E4ECEFB6EA850D95EB785938CD3E3AFEF9EF8B08AF219C1496633193468AB755CB") 248 249def cli_bcrypt_tests(_tmp_dir): 250 test_cli("gen_bcrypt", "--work-factor=4 s3kr1t", 251 "$2a$04$0.8G7o08XYwvBBWA3l0WUujtwoGZgGDzVSN8fNkNqXikcK4A3lHPS") 252 253 test_cli("check_bcrypt", "s3kr1t $2a$04$gHX4Qg7pDSJuXiPXnmt8leyb.FFzX1Bv4rXwIj2cPSakJ8zNnhIka", 254 "Password is valid") 255 256 test_cli("check_bcrypt", "santa $2a$04$gHX4Qg7pDSJuXiPXnmt8leyb.FFzX1Bv4rXwIj2cPSakJ8zNnhIka", 257 "Password is NOT valid") 258 259def cli_argon2_tests(_tmp_dir): 260 password = "s3kr1t" 261 expected = "$argon2id$v=19$m=8,t=1,p=1$2A+I9q2+ZayxDDYC5n2YWw$/Lhx+Jbtlpw+Kxpskfv7+AKhBL/5ebalTJkVC1O5+1E" 262 test_cli("gen_argon2", ['--mem=8', password], expected) 263 test_cli("gen_argon2", ['--mem=8', '--t=1', password], expected) 264 test_cli("gen_argon2", ['--mem=8', '--t=1', '--p=1', password], expected) 265 266 test_cli("check_argon2", [password, expected], "Password is valid") 267 test_cli("check_argon2", ["guessing", expected], "Password is NOT valid") 268 269def cli_gen_dl_group_tests(_tmp_dir): 270 271 pem = """-----BEGIN X9.42 DH PARAMETERS----- 272MIIBJAKBgwTw7LQiLkXJsrgMVQxTPlWaQlYz/raZ+5RtIZe4YluQgRQGPFADLZ/t 273TOYzuIzZJFOcdKtEtrVkxZRGSkjZwKFKLUD6fzSjoC2M2EHktK/y5HsvxBxL4tKr 274q1ffbyPQi+iBLYTZAXygvxj2vWyrvA+/w4nbt1fStCHTDhWjLWqFpV9nAoGDAKzA 275HUu/IRl7OiUtW/dz36gzEJnaYtz4ZtJl0FG8RJiOe02lD8myqW2sVzYqMvKD0LGx 276x9fdSKC1G+aZ/NWtqrQjb66Daf7b0ddDx+bfWTWJ2dOtZd8IL2rmQQJm+JogDi9i 277huVYFicDNQGzi+nEKAzrZ1L/VxtiSiw/qw0IyOuVtz8CFjgPiPatvmWssQw2AuZ9 278mFvAZ/8wal0= 279-----END X9.42 DH PARAMETERS-----""" 280 281 test_cli("gen_dl_group", "--pbits=1043", pem) 282 283 dsa_grp = """-----BEGIN X9.42 DH PARAMETERS----- 284MIIBHgKBgQCyP1vosC/axliM2hmJ9EOSdd1zBkuzMP25CYD8PFkRVrPLr1ClSUtn 285eXTIsHToJ7d7sRwtidQGW9BrvUEyiAWE06W/wnLPxB3/g2/l/P2EhbNmNHAO7rV7 286ZVz/uKR4Xcvzxg9uk5MpT1VsxA8H6VEwzefNF1Rya92rqGgBTNT3/wKBgC7HLL8A 287Gu3tqJxTk1iNgojjOiSreLn6ihA8R8kQnRXDTNtDKz996KHGInfMBurUI1zPM3xq 288bHc0CvU1Nf87enhPIretzJcFgiCWrNFUIC25zPEjp0s3/ERHT4Bi1TABZ3j6YUEQ 289fnnj+9XriKKHf2WtX0T4FXorvnKq30m934rzAhUAvwhWDK3yZEmphc7dwl4/J3Zp 290+MU= 291-----END X9.42 DH PARAMETERS-----""" 292 293 test_cli("gen_dl_group", ["--type=dsa", "--pbits=1024"], dsa_grp) 294 295 296def cli_key_tests(tmp_dir): 297 298 pem = """-----BEGIN PRIVATE KEY----- 299MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQg2A+I9q2+ZayxDDYC5n2Y 300W8Bn/zBm4D3mwS5qMwADRDehRANCAATwnDFqsjXL9SD/Rr1Vy4pb79PswXdQNZBN 301mlLtJ5JvZ0/p6zP3x+Y9yPIrAR8L/acG5ItSrAKXzzuqQQZMv4aN 302-----END PRIVATE KEY-----""" 303 304 priv_key = os.path.join(tmp_dir, 'priv.pem') 305 pub_key = os.path.join(tmp_dir, 'pub.pem') 306 pub_der_key = os.path.join(tmp_dir, 'pub.der') 307 enc_pem = os.path.join(tmp_dir, 'priv_enc.pem') 308 enc_der = os.path.join(tmp_dir, 'priv_enc.der') 309 ca_cert = os.path.join(tmp_dir, 'ca.crt') 310 crt_req = os.path.join(tmp_dir, 'crt.req') 311 user_cert = os.path.join(tmp_dir, 'user.crt') 312 313 test_cli("keygen", ["--algo=ECDSA", "--params=secp256k1"], pem) 314 315 test_cli("keygen", ["--algo=ECDSA", "--params=secp256r1", "--output=" + priv_key], "") 316 317 test_cli("pkcs8", "--pub-out --output=%s %s" % (pub_key, priv_key), "") 318 test_cli("pkcs8", "--pub-out --der-out --output=%s %s" % (pub_der_key, priv_key), "") 319 320 test_cli("pkcs8", "--pass-out=foof --der-out --output=%s %s" % (enc_der, priv_key), "") 321 test_cli("pkcs8", "--pass-out=foof --output=%s %s" % (enc_pem, priv_key), "") 322 323 dec_pem = test_cli("pkcs8", ["--pass-in=foof", enc_pem], None) 324 dec_der = test_cli("pkcs8", ["--pass-in=foof", enc_der], None) 325 326 if dec_pem != dec_der: 327 logging.error("Problem decrypting PKCS8 key") 328 329 test_cli("fingerprint", ['--no-fsname', pub_key], 330 "83:FC:67:87:30:C7:0C:9C:54:9A:E7:A1:FA:25:83:4C:77:A4:43:16:33:6D:47:3C:CE:4B:91:62:30:97:62:D4") 331 332 test_cli("fingerprint", ['--no-fsname', pub_der_key], 333 "83:FC:67:87:30:C7:0C:9C:54:9A:E7:A1:FA:25:83:4C:77:A4:43:16:33:6D:47:3C:CE:4B:91:62:30:97:62:D4") 334 335 test_cli("fingerprint", ['--no-fsname', pub_key, pub_der_key], 336 "83:FC:67:87:30:C7:0C:9C:54:9A:E7:A1:FA:25:83:4C:77:A4:43:16:33:6D:47:3C:CE:4B:91:62:30:97:62:D4\n" 337 "83:FC:67:87:30:C7:0C:9C:54:9A:E7:A1:FA:25:83:4C:77:A4:43:16:33:6D:47:3C:CE:4B:91:62:30:97:62:D4") 338 339 test_cli("fingerprint", [pub_der_key], 340 pub_der_key + 341 ": 83:FC:67:87:30:C7:0C:9C:54:9A:E7:A1:FA:25:83:4C:77:A4:43:16:33:6D:47:3C:CE:4B:91:62:30:97:62:D4") 342 343 test_cli("fingerprint", ['-'], 344 "83:FC:67:87:30:C7:0C:9C:54:9A:E7:A1:FA:25:83:4C:77:A4:43:16:33:6D:47:3C:CE:4B:91:62:30:97:62:D4", 345 open(pub_key, 'rb').read().decode()) 346 347 valid_sig = "nI4mI1ec14Y7nYUWs2edysAVvkob0TWpmGh5rrYWDA+/W9Fj0ZM21qJw8qa3/avAOIVBO6hoMEVmfJYXlS+ReA==" 348 349 test_cli("sign", "--provider=base %s %s" % (priv_key, pub_key), valid_sig) 350 351 test_cli("verify", [pub_key, pub_key, '-'], 352 "Signature is valid", valid_sig) 353 354 test_cli("verify", [pub_key, pub_key, '-'], 355 "Signature is invalid", 356 valid_sig.replace("G", "H")) 357 358 test_cli("gen_self_signed", 359 [priv_key, "CA", "--ca", "--country=VT", 360 "--dns=ca.example", "--hash=SHA-384", "--output="+ca_cert], 361 "") 362 363 test_cli("cert_verify", ca_cert, "Certificate did not validate - Cannot establish trust") 364 365 cert_info = test_cli("cert_info", ['--fingerprint', ca_cert], None) 366 367 if cert_info.find('Subject: CN="CA",C="VT"') < 0: 368 logging.error('Unexpected output for cert_info command %s', cert_info) 369 if cert_info.find('Subject keyid: 69DD911C9EEE3400C67CBC3F3056CBE711BD56AF9495013F') < 0: 370 logging.error('Unexpected output for cert_info command %s', cert_info) 371 372 test_cli("gen_pkcs10", "%s User --output=%s" % (priv_key, crt_req)) 373 374 test_cli("sign_cert", "%s %s %s --output=%s" % (ca_cert, priv_key, crt_req, user_cert)) 375 376 test_cli("cert_verify", [user_cert, ca_cert], 377 "Certificate passes validation checks") 378 379 test_cli("cert_verify", user_cert, 380 "Certificate did not validate - Certificate issuer not found") 381 382def cli_xmss_sign_tests(tmp_dir): 383 priv_key = os.path.join(tmp_dir, 'priv.pem') 384 pub_key = os.path.join(tmp_dir, 'pub.pem') 385 pub_key2 = os.path.join(tmp_dir, 'pub2.pem') 386 msg = os.path.join(tmp_dir, 'input') 387 sig1 = os.path.join(tmp_dir, 'sig1') 388 sig2 = os.path.join(tmp_dir, 'sig2') 389 390 test_cli("rng", ['--output=%s' % (msg)], "") 391 test_cli("hash", ["--no-fsname", msg], "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855") 392 393 test_cli("keygen", ["--algo=XMSS", "--output=%s" % (priv_key)], "") 394 test_cli("hash", ["--no-fsname", priv_key], "5B38F737BA41BE7F40433DB30EAEF7C41ABB0F7D9E7A09DEB5FDCE7B6811693F") 395 396 test_cli("pkcs8", "--pub-out --output=%s %s" % (pub_key, priv_key), "") 397 test_cli("fingerprint", ['--no-fsname', pub_key], 398 "B0:F4:98:6E:D8:4E:05:63:A1:D8:4B:37:61:5A:A0:41:78:7E:DE:0E:72:46:E0:A8:D6:CF:09:54:08:DA:A4:22") 399 400 # verify the key is updated after each signature: 401 test_cli("sign", [priv_key, msg, "--output=%s" % (sig1)], "") 402 test_cli("verify", [pub_key, msg, sig1], "Signature is valid") 403 test_cli("hash", ["--no-fsname", sig1], "04AF45451C7A9AF2D828E1AD6EC262E012436F4087C5DA6F32C689D781E597D0") 404 test_cli("hash", ["--no-fsname", priv_key], "67929FAEC636E43DE828C1CD7E2D11CE7C3388CE90DD0A0F687C6627FFA850CD") 405 406 test_cli("sign", [priv_key, msg, "--output=%s" % (sig2)], "") 407 test_cli("verify", [pub_key, msg, sig2], "Signature is valid") 408 test_cli("hash", ["--no-fsname", sig2], "0785A6AD54CC7D01F2BE2BC6463A3EAA1159792E52210ED754992C5068E8F24F") 409 test_cli("hash", ["--no-fsname", priv_key], "1940945D68B1CF54D79E05DD7913A4D0B4959183F1E12B81A4E43EF4E63FBD20") 410 411 # private key updates, public key is unchanged: 412 test_cli("pkcs8", "--pub-out --output=%s %s" % (pub_key2, priv_key), "") 413 test_cli("fingerprint", ['--no-fsname', pub_key2], 414 "B0:F4:98:6E:D8:4E:05:63:A1:D8:4B:37:61:5A:A0:41:78:7E:DE:0E:72:46:E0:A8:D6:CF:09:54:08:DA:A4:22") 415 416def cli_pbkdf_tune_tests(_tmp_dir): 417 if not check_for_command("pbkdf_tune"): 418 return 419 420 expected = re.compile(r'For (default|[1-9][0-9]*) ms selected Scrypt\([0-9]+,[0-9]+,[0-9]+\) using [0-9]+ MiB') 421 422 output = test_cli("pbkdf_tune", ["--check", "1", "10", "50", "default"], None).split('\n') 423 424 for line in output: 425 if expected.match(line) is None: 426 logging.error("Unexpected line '%s'" % (line)) 427 428 expected_pbkdf2 = re.compile(r'For (default|[1-9][0-9]*) ms selected PBKDF2\(HMAC\(SHA-256\),[0-9]+\)') 429 430 output = test_cli("pbkdf_tune", ["--algo=PBKDF2(SHA-256)", "--check", "1", "10", "50", "default"], None).split('\n') 431 432 for line in output: 433 if expected_pbkdf2.match(line) is None: 434 logging.error("Unexpected line '%s'" % (line)) 435 436 expected_argon2 = re.compile(r'For (default|[1-9][0-9]*) ms selected Argon2id\([0-9]+,[0-9]+,[0-9]+\)') 437 438 output = test_cli("pbkdf_tune", ["--algo=Argon2id", "--check", "1", "10", "50", "default"], None).split('\n') 439 440 for line in output: 441 if expected_argon2.match(line) is None: 442 logging.error("Unexpected line '%s'" % (line)) 443 444def cli_psk_db_tests(tmp_dir): 445 if not check_for_command("psk_get"): 446 return 447 448 psk_db = os.path.join(tmp_dir, 'psk.db') 449 db_key1 = "909"*32 450 db_key2 = "451"*32 451 452 test_cli("psk_set", [psk_db, db_key1, "name", "F00FEE"], "") 453 test_cli("psk_set", [psk_db, db_key2, "name", "C00FEE11"], "") 454 test_cli("psk_set", [psk_db, db_key1, "name2", "50051029"], "") 455 456 test_cli("psk_get", [psk_db, db_key1, "name"], "F00FEE") 457 test_cli("psk_get", [psk_db, db_key2, "name"], "C00FEE11") 458 459 test_cli("psk_list", [psk_db, db_key1], "name\nname2") 460 test_cli("psk_list", [psk_db, db_key2], "name") 461 462def cli_compress_tests(tmp_dir): 463 464 if not check_for_command("compress"): 465 return 466 467 input_file = os.path.join(tmp_dir, 'input.txt') 468 output_file = os.path.join(tmp_dir, 'input.txt.gz') 469 470 with open(input_file, 'w') as f: 471 f.write("hi there") 472 f.close() 473 474 test_cli("compress", input_file) 475 476 if not os.access(output_file, os.R_OK): 477 logging.error("Compression did not created expected output file") 478 479 is_py3 = sys.version_info[0] == 3 480 481 output_hdr = open(output_file, 'rb').read(2) 482 483 if is_py3: 484 if output_hdr[0] != 0x1F or output_hdr[1] != 0x8B: 485 logging.error("Did not see expected gzip header") 486 else: 487 if ord(output_hdr[0]) != 0x1F or ord(output_hdr[1]) != 0x8B: 488 logging.error("Did not see expected gzip header") 489 490 os.unlink(input_file) 491 492 test_cli("decompress", output_file) 493 494 if not os.access(input_file, os.R_OK): 495 logging.error("Decompression did not created expected output file") 496 497 recovered = open(input_file).read() 498 if recovered != "hi there": 499 logging.error("Decompression did not recover original input") 500 501def cli_rng_tests(_tmp_dir): 502 test_cli("rng", "10", "D80F88F6ADBE65ACB10C") 503 test_cli("rng", "16", "D80F88F6ADBE65ACB10C3602E67D985B") 504 test_cli("rng", "10 6", "D80F88F6ADBE65ACB10C\n1B119CC068AF") 505 506 test_cli("rng", ['--format=base64', '10'], "2A+I9q2+ZayxDA==") 507 test_cli("rng", ['--format=base58', '10'], "D93XRyVfxqs7oR") 508 test_cli("rng", ['--format=base58check', '10'], "2NS1jYUq92TyGFVnhVLa") 509 510 hex_10 = re.compile('[A-F0-9]{20}') 511 512 for rng in ['system', 'auto', 'entropy']: 513 output = test_cli("rng", ["10", '--%s' % (rng)], use_drbg=False) 514 if output == "D80F88F6ADBE65ACB10C": 515 logging.error('RNG produced DRBG output') 516 if hex_10.match(output) is None: 517 logging.error('Unexpected RNG output %s' % (output)) 518 519 has_rdrand = test_cli("cpuid", []).find(' rdrand ') > 0 520 521 if has_rdrand: 522 output = test_cli("rng", ["10", '--rdrand'], use_drbg=False) 523 524 if output == "D80F88F6ADBE65ACB10C": 525 logging.error('RDRAND produced DRBG output') 526 if hex_10.match(output) is None: 527 logging.error('Unexpected RNG output %s' % (output)) 528 529def cli_roughtime_check_tests(tmp_dir): 530 # pylint: disable=line-too-long 531 if not check_for_command("roughtime_check"): 532 return 533 chain = os.path.join(tmp_dir, 'roughtime-chain') 534 535 with open(chain, 'w') as f: 536 f.write("""\ 537ed25519 bbT+RPS7zKX6w71ssPibzmwWqU9ffRV5oj2OresSmhE= eu9yhsJfVfguVSqGZdE8WKIxaBBM0ZG3Vmuc+IyZmG2YVmrIktUByDdwIFw6F4rZqmSFsBO85ljoVPz5bVPCOw== BQAAAEAAAABAAAAApAAAADwBAABTSUcAUEFUSFNSRVBDRVJUSU5EWBnGOEajOwPA6G7oL47seBP4C7eEpr57H43C2/fK/kMA0UGZVUdf4KNX8oxOK6JIcsbVk8qhghTwA70qtwpYmQkDAAAABAAAAAwAAABSQURJTUlEUFJPT1RAQg8AJrA8tEqPBQAqisiuAxgy2Pj7UJAiWbCdzGz1xcCnja3T+AqhC8fwpeIwW4GPy/vEb/awXW2DgSLKJfzWIAz+2lsR7t4UjNPvAgAAAEAAAABTSUcAREVMRes9Ch4X0HIw5KdOTB8xK4VDFSJBD/G9t7Et/CU7UW61OiTBXYYQTG2JekWZmGa0OHX1JPGG+APkpbsNw0BKUgYDAAAAIAAAACgAAABQVUJLTUlOVE1BWFR/9BWjpsWTQ1f6iUJea3EfZ1MkX3ftJiV3ABqNLpncFwAAAAAAAAAA//////////8AAAAA 538ed25519 gD63hSj3ScS+wuOeGrubXlq35N1c5Lby/S+T7MNTjxo= uLeTON9D+2HqJMzK6sYWLNDEdtBl9t/9yw1cVAOm0/sONH5Oqdq9dVPkC9syjuWbglCiCPVF+FbOtcxCkrgMmA== BQAAAEAAAABAAAAApAAAADwBAABTSUcAUEFUSFNSRVBDRVJUSU5EWOw1jl0uSiBEH9HE8/6r7zxoSc01f48vw+UzH8+VJoPelnvVJBj4lnH8uRLh5Aw0i4Du7XM1dp2u0r/I5PzhMQoDAAAABAAAAAwAAABSQURJTUlEUFJPT1RAQg8AUBo+tEqPBQC47l77to7ESFTVhlw1SC74P5ssx6gpuJ6eP+1916GuUiySGE/x3Fp0c3otUGAdsRQou5p9PDTeane/YEeVq4/8AgAAAEAAAABTSUcAREVMRe5T1ml8wHyWAcEtHP/U5Rg/jFXTEXOSglngSa4aI/CECVdy4ZNWeP6vv+2//ZW7lQsrWo7ZkXpvm9BdBONRSQIDAAAAIAAAACgAAABQVUJLTUlOVE1BWFQpXlenV0OfVisvp9jDHXLw8vymZVK9Pgw9k6Edf8ZEhUgSGEc5jwUASHLvZE2PBQAAAAAA 539ed25519 etPaaIxcBMY1oUeGpwvPMCJMwlRVNxv51KK/tktoJTQ= U53wX99JzZwy4BXa9C6R04bPu4yqFB5w5/wTgG8Mw5wm+VLrY70ECxJ9ZHnpdHVHaLEU3aeLnQFZyZPRAEOCyw== BQAAAEAAAABAAAAApAAAADwBAABTSUcAUEFUSFNSRVBDRVJUSU5EWMh3mPWCCbOlX8xDWbU9qdfKoReJX/XLsivom8bJJYmcC7T03tyXrtWUheEJweHtg4qMgSyifQS1MjHJSy1jPAsDAAAABAAAAAwAAABSQURJTUlEUFJPT1RAQg8Akxw/tEqPBQBfOsOuciR7jiAW5itQ39y8yVr/ZJmgMwvTjqaU4/wA05ZqG4RqoLdvDXh5bCNySL6LrrnBNSAHwn5COt0CItNuAgAAAEAAAABTSUcAREVMRVP3BIOzsZmuxqMi+ScIBPyKtzFfK7ZlPFNP0JrNwln2QYtAcQFIKywDdNAAL+n8i3dz1p99K50FJjCkCl2J6AMDAAAAIAAAACgAAABQVUJLTUlOVE1BWFQKC/kZVdjiNT2NCSGfnpot4eqipyMFsyMjiIQmqqqXqQCAa245jwUAAGCgA56PBQAAAAAA 540ed25519 AW5uAoTSTDfG5NfY1bTh08GUnOqlRb+HVhbJ3ODJvsE= IcZcXFuaLKYYhWcK3sT/6PrVeXMmabCRbf9hvVfkMkqEW1PFL++ZnHJ1/m+G8azITxvktwsfP1YAOOxWdbf9XQ== BQAAAEAAAABAAAAApAAAADwBAABTSUcAUEFUSFNSRVBDRVJUSU5EWL5DAl8GPNUQ/mSXl0tI4N9yZAO+PiXTodJOTDL+WU/x26iqgyyQRikSSocRMzAEVLDGasdyW19mVC6H/6vfXggDAAAABAAAAAwAAABSQURJTUlEUFJPT1RAQg8Av/JAtEqPBQBIP346SHhCdDfughzeH+uYSbxngDYxqHzBDtZt0obUKrzxfRWzD1oR61B1reLvoPVCKSfzEngi/g1NSQjTrzNMAgAAAEAAAABTSUcAREVMRTQLLplQv0rN4p77Bo59qT8bbquV6MKSwILI/Tw2LLGo9noaZegUFmM+rNu1d1AVOEVQ01j6/2xDmBvp0d6MZgEDAAAAIAAAACgAAABQVUJLTUlOVE1BWFS4a1dYoIB5u/zkbR3sIteuhVrQkszzj+Gng9ywo6O9VgAAAAAAAAAA//////////8AAAAA 541ed25519 cj8GsiNlRkqiDElAeNMSBBMwrAl15hYPgX50+GWX/lA= Tsy82BBU2xxVqNe1ip11OyEGoKWhKoSggWjBmDTSBmKbTs7bPPCEidYc5TQ23sQUWe62G35fQOVU28q+Eq5uhQ== BQAAAEAAAABAAAAApAAAADwBAABTSUcAUEFUSFNSRVBDRVJUSU5EWDAmi7zgXAqLgQXVfbjeqnUZRiXCZI64QIoAKFL83CQHbyXgB4cNwHfQ9mSg0hYxTp1M8QxOuzusnUpk05DIRwwDAAAABAAAAAwAAABSQURJTUlEUFJPT1RAQg8AcOBCtEqPBQBhsr1mKOxxCf4VDFzAtYB4Nhs332AN1LrJU/8+VqktzfPd2R7awJHEVEWugvSvOrr+9d332mQObAkYfKfDtbSFAgAAAEAAAABTSUcAREVMRUjnhDvkIjFzTEYtgHOfMpRHtnNZj4P31RFtapkwzGjOtc93pYDd7zqQCw2AVcfbSnPqa8k26z96Q9fVRzq0pw8DAAAAIAAAACgAAABQVUJLTUlOVE1BWFR7qp2oerjpbN8Y23nUGARIlsgkodW4owH29ZKhxDMn8AAAAAAAAAAA//////////8AAAAA 542""") 543 544 test_cli("roughtime_check", chain, """\ 5451: UTC 2019-08-04T13:38:17 (+-1000000us) 546 2: UTC 2019-08-04T13:38:17 (+-1000000us) 547 3: UTC 2019-08-04T13:38:17 (+-1000000us) 548 4: UTC 2019-08-04T13:38:18 (+-1000000us) 549 5: UTC 2019-08-04T13:38:18 (+-1000000us)""") 550 551 with open(chain, 'w') as f: 552 f.write("ed25519 bbT+RPS7zKX6w71ssPibzmwWqU9ffRV5oj2OresSmhE= eu9yhsJfVfguVSqGZdE8WKIxaBBM0ZG3Vmuc+IyZmG2YVmrIktUByDdwIFw6F4rZqmSFsBO85ljoVPz5bVPCOw== BQAAAEAAAABAAAAApAAAADwBAABTSUcAUEFUSFNSRVBDRVJUSU5EWBnGOEajOwPA6G7oL47seBP4C7eEpr57H43C2/fK/kMA0UGZVUdf4KNX8oxOK6JIcsbVk8qhghTwA70qtwpYmQkDAAAABAAAAAwAAABSQURJTUlEUFJPT1RAQg8AJrA8tEqPBQAqisiuAxgy2Pj7UJAiWbCdzGz1xcCnja3T+AqhC8fwpeIwW4GPy/vEb/awXW2DgSLKJfzWIAz+2lsR7t4UjNPvAgAAAEAAAABTSUcAREVMRes9Ch4X0HIw5KdOTB8xK4VDFSJBD/G9t7Et/CU7UW61OiTBXYYQTG2JekWZmGa0OHX1JPGG+APkpbsNw0BKUgYDAAAAIAAAACgAAABQVUJLTUlOVE1BWFR/9BWjpsWTQ1f6iUJea3EfZ1MkX3ftJiV3ABqNLpncFwAAAAAAAAAA//////////8AAAAA") 553 test_cli("roughtime_check", [chain, "--raw-time"], "1: UTC 1564925897781286 (+-1000000us)") 554 555 with open(chain, 'w') as f: 556 f.write("ed25519 cbT+RPS7zKX6w71ssPibzmwWqU9ffRV5oj2OresSmhE= eu9yhsJfVfguVSqGZdE8WKIxaBBM0ZG3Vmuc+IyZmG2YVmrIktUByDdwIFw6F4rZqmSFsBO85ljoVPz5bVPCOw== BQAAAEAAAABAAAAApAAAADwBAABTSUcAUEFUSFNSRVBDRVJUSU5EWBnGOEajOwPA6G7oL47seBP4C7eEpr57H43C2/fK/kMA0UGZVUdf4KNX8oxOK6JIcsbVk8qhghTwA70qtwpYmQkDAAAABAAAAAwAAABSQURJTUlEUFJPT1RAQg8AJrA8tEqPBQAqisiuAxgy2Pj7UJAiWbCdzGz1xcCnja3T+AqhC8fwpeIwW4GPy/vEb/awXW2DgSLKJfzWIAz+2lsR7t4UjNPvAgAAAEAAAABTSUcAREVMRes9Ch4X0HIw5KdOTB8xK4VDFSJBD/G9t7Et/CU7UW61OiTBXYYQTG2JekWZmGa0OHX1JPGG+APkpbsNw0BKUgYDAAAAIAAAACgAAABQVUJLTUlOVE1BWFR/9BWjpsWTQ1f6iUJea3EfZ1MkX3ftJiV3ABqNLpncFwAAAAAAAAAA//////////8AAAAA") 557 test_cli("roughtime_check", chain, expected_stderr=b'Error: Roughtime Invalid signature or public key\n') 558 559def cli_roughtime_tests(tmp_dir): 560 # pylint: disable=line-too-long 561 # pylint: disable=too-many-locals 562 import socket 563 import base64 564 import threading 565 566 if not check_for_command("roughtime"): 567 return 568 569 server_port = random_port_number() 570 chain_file = os.path.join(tmp_dir, 'roughtime-chain') 571 ecosystem = os.path.join(tmp_dir, 'ecosystem') 572 573 def run_udp_server(): 574 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 575 server_address = ('127.0.0.1', server_port) 576 sock.bind(server_address) 577 578 while True: 579 data, address = sock.recvfrom(4096) 580 581 if data: 582 if data != base64.b64decode(server_request): 583 logging.error("unexpected request") 584 585 sock.sendto(base64.b64decode(server_response), address) 586 587 udp_thread = threading.Thread(target=run_udp_server) 588 udp_thread.daemon = True 589 udp_thread.start() 590 591 chain = [ 592 """\ 593ed25519 gD63hSj3ScS+wuOeGrubXlq35N1c5Lby/S+T7MNTjxo= 2A+I9q2+ZayxDDYC5n2YW8Bn/zBm4D3mwS5qMwADRDcbFpBcf3yPOyeZiqpLBTkxo8GT8zMQFeApv4ScffjC8A== BQAAAEAAAABAAAAApAAAADwBAABTSUcAUEFUSFNSRVBDRVJUSU5EWDwlo/AkUnTrecAW4Ci5Tkh3KOqs6R7KLTsFtq16RXN5F7G5ckGv11UtzHoZTbKbEk03a6ogAOK54Q2CI/7XGA8DAAAABAAAAAwAAABSQURJTUlEUFJPT1RAQg8AWDLihlaSBQAoq/5gEjRCrhfH16X2GYjQJSG/CgSuGhYeCsrw7XkphLI3cxw2unJRDW8DAJrYqEGaW0NPKZk7bbpPjU/Q6Es1AgAAAEAAAABTSUcAREVMRUJbs67Sb5Wx/jzWyT1PhWR0c4kg59tjSGofo8R3eHzcA9CGwavuRdxOArhVWWODG99gYgfmjcRLgt9/jH+99w4DAAAAIAAAACgAAABQVUJLTUlOVE1BWFRXRfQ1RHLWGOgqABUTYfVBDZrv3OL2nPLYve9ldfNVLOjdPVFFkgUA6D0Vb1mSBQAAAAAA 594""", 595 """\ 596ed25519 gD63hSj3ScS+wuOeGrubXlq35N1c5Lby/S+T7MNTjxo= 2A+I9q2+ZayxDDYC5n2YW8Bn/zBm4D3mwS5qMwADRDcbFpBcf3yPOyeZiqpLBTkxo8GT8zMQFeApv4ScffjC8A== BQAAAEAAAABAAAAApAAAADwBAABTSUcAUEFUSFNSRVBDRVJUSU5EWDwlo/AkUnTrecAW4Ci5Tkh3KOqs6R7KLTsFtq16RXN5F7G5ckGv11UtzHoZTbKbEk03a6ogAOK54Q2CI/7XGA8DAAAABAAAAAwAAABSQURJTUlEUFJPT1RAQg8AWDLihlaSBQAoq/5gEjRCrhfH16X2GYjQJSG/CgSuGhYeCsrw7XkphLI3cxw2unJRDW8DAJrYqEGaW0NPKZk7bbpPjU/Q6Es1AgAAAEAAAABTSUcAREVMRUJbs67Sb5Wx/jzWyT1PhWR0c4kg59tjSGofo8R3eHzcA9CGwavuRdxOArhVWWODG99gYgfmjcRLgt9/jH+99w4DAAAAIAAAACgAAABQVUJLTUlOVE1BWFRXRfQ1RHLWGOgqABUTYfVBDZrv3OL2nPLYve9ldfNVLOjdPVFFkgUA6D0Vb1mSBQAAAAAA 597ed25519 gD63hSj3ScS+wuOeGrubXlq35N1c5Lby/S+T7MNTjxo= 2A+I9q2+ZayxDDYC5n2YW8Bn/zBm4D3mwS5qMwADRDcbFpBcf3yPOyeZiqpLBTkxo8GT8zMQFeApv4ScffjC8A== BQAAAEAAAABAAAAApAAAADwBAABTSUcAUEFUSFNSRVBDRVJUSU5EWHH5Ofs4HciIFXjE9egjDbistJptoMXIC7ugCgHhI4NPJqfYY256NpULXKc9c30ul7oHXQyKLfGd84mIAxC3UwQDAAAABAAAAAwAAABSQURJTUlEUFJPT1RAQg8AuOoUh1aSBQANeC4gGGG3a23PpmF+y6CrUS9VWjyj0Ydpl2tMVDLaK2vd5QtYKKJ3UOyprGKk0D/aPn4E3Bk2rE3BKBZRXM1AAgAAAEAAAABTSUcAREVMRci9uvioJssgd8txxFlqz9RqPx+YLVMkHmm24fMUtYGWF/nhkoEYVGT7O+tXSfHHY/KHcUZjVaZpEt/tmXlXBAUDAAAAIAAAACgAAABQVUJLTUlOVE1BWFSxhKhavdriTvCAtNVcK5yr0cAbsWp2MsrwUV5YTc+7V0CsaLZSkgUAQAxA1GaSBQAAAAAA 598""", 599 """\ 600ed25519 gD63hSj3ScS+wuOeGrubXlq35N1c5Lby/S+T7MNTjxo= SbWKPilWYrt+1vgFU3jlxGNOH6I/1npX8wl+KoraN3S6VDsyM6EfCV+JPEK8BsNoM2VIpMcSdjcVna/GwXwZkg== BQAAAEAAAABAAAAApAAAADwBAABTSUcAUEFUSFNSRVBDRVJUSU5EWHH5Ofs4HciIFXjE9egjDbistJptoMXIC7ugCgHhI4NPJqfYY256NpULXKc9c30ul7oHXQyKLfGd84mIAxC3UwQDAAAABAAAAAwAAABSQURJTUlEUFJPT1RAQg8AuOoUh1aSBQANeC4gGGG3a23PpmF+y6CrUS9VWjyj0Ydpl2tMVDLaK2vd5QtYKKJ3UOyprGKk0D/aPn4E3Bk2rE3BKBZRXM1AAgAAAEAAAABTSUcAREVMRci9uvioJssgd8txxFlqz9RqPx+YLVMkHmm24fMUtYGWF/nhkoEYVGT7O+tXSfHHY/KHcUZjVaZpEt/tmXlXBAUDAAAAIAAAACgAAABQVUJLTUlOVE1BWFSxhKhavdriTvCAtNVcK5yr0cAbsWp2MsrwUV5YTc+7V0CsaLZSkgUAQAxA1GaSBQAAAAAA 601ed25519 gD63hSj3ScS+wuOeGrubXlq35N1c5Lby/S+T7MNTjxo= 2A+I9q2+ZayxDDYC5n2YW8Bn/zBm4D3mwS5qMwADRDcbFpBcf3yPOyeZiqpLBTkxo8GT8zMQFeApv4ScffjC8A== BQAAAEAAAABAAAAApAAAADwBAABTSUcAUEFUSFNSRVBDRVJUSU5EWN5Y0b2irPS1JgqJFQMciPg4aWd9qj1ZqcJc5bGXe1m4ZdAXa5OIhXa0+680MgpyhEHhqYJDIwH1XRa1OZx5YAUDAAAABAAAAAwAAABSQURJTUlEUFJPT1RAQg8AgBW3iFaSBQD9WI+Qr6NOZsDmP0PsnCo66mstM3ac5ZON+I+ZeEK8lZWBASvsD2JIfq3v4d1QH5g4STs3wOazQPc25Puy659ZAgAAAEAAAABTSUcAREVMRUJbs67Sb5Wx/jzWyT1PhWR0c4kg59tjSGofo8R3eHzcA9CGwavuRdxOArhVWWODG99gYgfmjcRLgt9/jH+99w4DAAAAIAAAACgAAABQVUJLTUlOVE1BWFRXRfQ1RHLWGOgqABUTYfVBDZrv3OL2nPLYve9ldfNVLOjdPVFFkgUA6D0Vb1mSBQAAAAAA 602""", 603 ] 604 request = [ 605 "AgAAAEAAAABOT05DUEFE/9gPiPatvmWssQw2AuZ9mFvAZ/8wZuA95sEuajMAA0Q3GxaQXH98jzsnmYqqSwU5MaPBk/MzEBXgKb+EnH34wvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", 606 "AgAAAEAAAABOT05DUEFE/0m1ij4pVmK7ftb4BVN45cRjTh+iP9Z6V/MJfiqK2jd0ulQ7MjOhHwlfiTxCvAbDaDNlSKTHEnY3FZ2vxsF8GZIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", 607 "AgAAAEAAAABOT05DUEFE/0AcDP0F/L7NTiOCQlHovyMlovVtG4lBRqAgydNYk9WOoanOwclZuV8z2b/SCHj5thxbSNxuLNZoDQ2b6TWgPfsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", 608 ] 609 response = [ 610 "BQAAAEAAAABAAAAApAAAADwBAABTSUcAUEFUSFNSRVBDRVJUSU5EWDwlo/AkUnTrecAW4Ci5Tkh3KOqs6R7KLTsFtq16RXN5F7G5ckGv11UtzHoZTbKbEk03a6ogAOK54Q2CI/7XGA8DAAAABAAAAAwAAABSQURJTUlEUFJPT1RAQg8AWDLihlaSBQAoq/5gEjRCrhfH16X2GYjQJSG/CgSuGhYeCsrw7XkphLI3cxw2unJRDW8DAJrYqEGaW0NPKZk7bbpPjU/Q6Es1AgAAAEAAAABTSUcAREVMRUJbs67Sb5Wx/jzWyT1PhWR0c4kg59tjSGofo8R3eHzcA9CGwavuRdxOArhVWWODG99gYgfmjcRLgt9/jH+99w4DAAAAIAAAACgAAABQVUJLTUlOVE1BWFRXRfQ1RHLWGOgqABUTYfVBDZrv3OL2nPLYve9ldfNVLOjdPVFFkgUA6D0Vb1mSBQAAAAAA", 611 "BQAAAEAAAABAAAAApAAAADwBAABTSUcAUEFUSFNSRVBDRVJUSU5EWHH5Ofs4HciIFXjE9egjDbistJptoMXIC7ugCgHhI4NPJqfYY256NpULXKc9c30ul7oHXQyKLfGd84mIAxC3UwQDAAAABAAAAAwAAABSQURJTUlEUFJPT1RAQg8AuOoUh1aSBQANeC4gGGG3a23PpmF+y6CrUS9VWjyj0Ydpl2tMVDLaK2vd5QtYKKJ3UOyprGKk0D/aPn4E3Bk2rE3BKBZRXM1AAgAAAEAAAABTSUcAREVMRci9uvioJssgd8txxFlqz9RqPx+YLVMkHmm24fMUtYGWF/nhkoEYVGT7O+tXSfHHY/KHcUZjVaZpEt/tmXlXBAUDAAAAIAAAACgAAABQVUJLTUlOVE1BWFSxhKhavdriTvCAtNVcK5yr0cAbsWp2MsrwUV5YTc+7V0CsaLZSkgUAQAxA1GaSBQAAAAAA", 612 "BQAAAEAAAABAAAAApAAAADwBAABTSUcAUEFUSFNSRVBDRVJUSU5EWN5Y0b2irPS1JgqJFQMciPg4aWd9qj1ZqcJc5bGXe1m4ZdAXa5OIhXa0+680MgpyhEHhqYJDIwH1XRa1OZx5YAUDAAAABAAAAAwAAABSQURJTUlEUFJPT1RAQg8AgBW3iFaSBQD9WI+Qr6NOZsDmP0PsnCo66mstM3ac5ZON+I+ZeEK8lZWBASvsD2JIfq3v4d1QH5g4STs3wOazQPc25Puy659ZAgAAAEAAAABTSUcAREVMRUJbs67Sb5Wx/jzWyT1PhWR0c4kg59tjSGofo8R3eHzcA9CGwavuRdxOArhVWWODG99gYgfmjcRLgt9/jH+99w4DAAAAIAAAACgAAABQVUJLTUlOVE1BWFRXRfQ1RHLWGOgqABUTYfVBDZrv3OL2nPLYve9ldfNVLOjdPVFFkgUA6D0Vb1mSBQAAAAAA", 613 ] 614 615 server_request = request[0] 616 server_response = response[0] 617 test_cli("roughtime", [], expected_stderr=b'Please specify either --servers-file or --host and --pubkey\n') 618 619 with open(ecosystem, 'w') as f: 620 f.write("Cloudflare-Roughtime ed25519 gD63hSj4ScS+wuOeGrubXlq35N1c5Lby/S+T7MNTjxo= udp 127.0.0.1:" + str(server_port)) 621 622 test_cli("roughtime", [ 623 "--check-local-clock=0", 624 "--chain-file=", 625 "--servers-file=" + ecosystem] 626 , expected_stderr=b'ERROR: Public key does not match!\n') 627 628 with open(ecosystem, 'w') as f: 629 f.write("Cloudflare-Roughtime ed25519 gD63hSj3ScS+wuOeGrubXlq35N1c5Lby/S+T7MNTjxo= udp 127.0.0.1:" + str(server_port)) 630 631 test_cli("roughtime", [ 632 "--chain-file=", 633 "--servers-file=" + ecosystem] 634 , expected_stderr=b'ERROR: Local clock mismatch\n') 635 636 test_cli("roughtime", [ 637 "--check-local-clock=0", 638 "--chain-file=" + chain_file, 639 "--servers-file=" + ecosystem] 640 , "Cloudflare-Roughtime : UTC 2019-09-12T08:00:11 (+-1000000us)") 641 642 with open(chain_file, 'r') as f: 643 read_data = f.read() 644 if read_data != chain[0]: 645 logging.error("unexpected chain") 646 647 server_request = request[1] 648 server_response = response[1] 649 test_cli("roughtime", [ 650 "--check-local-clock=0", 651 "--chain-file=" + chain_file, 652 "--host=127.0.0.1:" + str(server_port), 653 "--pubkey=gD63hSj3ScS+wuOeGrubXlq35N1c5Lby/S+T7MNTjxo=", 654 "--raw-time"] 655 , "UTC 1568275214691000 (+-1000000us)") 656 657 with open(chain_file, 'r') as f: 658 read_data = f.read() 659 if read_data != chain[1]: 660 logging.error("unexpected chain") 661 662 server_request = request[2] 663 server_response = response[2] 664 test_cli("roughtime", [ 665 "--check-local-clock=0", 666 "--chain-file=" + chain_file, 667 "--host=127.0.0.1:" + str(server_port), 668 "--pubkey=gD63hSj3ScS+wuOeGrubXlq35N1c5Lby/S+T7MNTjxo=", 669 "--max-chain-size=2"] 670 , "UTC 2019-09-12T08:00:42 (+-1000000us)") 671 672 with open(chain_file, 'r') as f: 673 read_data = f.read() 674 if read_data != chain[2]: 675 logging.error("unexpected chain") 676 677def cli_pk_workfactor_tests(_tmp_dir): 678 test_cli("pk_workfactor", "1024", "80") 679 test_cli("pk_workfactor", "2048", "111") 680 test_cli("pk_workfactor", ["--type=rsa", "512"], "58") 681 test_cli("pk_workfactor", ["--type=dl", "512"], "58") 682 test_cli("pk_workfactor", ["--type=dl_exp", "512"], "128") 683 684def cli_dl_group_info_tests(_tmp_dir): 685 686 dl_output = re.compile('(P|G) = [A-F0-9]+') 687 688 for bits in [1024, 1536, 2048, 3072, 4096, 6144, 8192]: 689 output = test_cli("dl_group_info", "modp/ietf/%d" % (bits)) 690 lines = output.split('\n') 691 692 if len(lines) != 2: 693 logging.error('Unexpected output from dl_group_info') 694 695 for l in lines: 696 if not dl_output.match(l): 697 logging.error('Unexpected output from dl_group_info') 698 699 700 701def cli_ec_group_info_tests(_tmp_dir): 702 703 # pylint: disable=line-too-long 704 secp256r1_info = """P = FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF 705A = FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC 706B = 5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B 707N = FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 708G = 6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296,4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5""" 709 710 secp256r1_pem = """-----BEGIN EC PARAMETERS----- 711MIHgAgEBMCwGByqGSM49AQECIQD/////AAAAAQAAAAAAAAAAAAAAAP////////// 712/////zBEBCD/////AAAAAQAAAAAAAAAAAAAAAP///////////////AQgWsY12Ko6 713k+ez671VdpiGvGUdBrDMU7D2O848PifSYEsEQQRrF9Hy4SxCR/i85uVjpEDydwN9 714gS3rM6D0oTlF2JjClk/jQuL+Gn+bjufrSnwPnhYrzjNXazFezsu2QGg3v1H1AiEA 715/////wAAAAD//////////7zm+q2nF56E87nKwvxjJVECAQE= 716-----END EC PARAMETERS-----""" 717 718 test_cli("ec_group_info", "secp256r1", secp256r1_info) 719 test_cli("ec_group_info", "--pem secp256r1", secp256r1_pem) 720 721def cli_cpuid_tests(_tmp_dir): 722 cpuid_output = test_cli("cpuid", []) 723 724 if not cpuid_output.startswith('CPUID flags:'): 725 logging.error('Unexpected cpuid output "%s"' % (cpuid_output)) 726 727 flag_re = re.compile('[a-z0-9_]+') 728 flags = cpuid_output[13:].split(' ') 729 for flag in flags: 730 if flag != '' and flag_re.match(flag) is None: 731 logging.error('Unexpected CPUID flag name "%s"' % (flag)) 732 733def cli_cc_enc_tests(_tmp_dir): 734 test_cli("cc_encrypt", ["8028028028028029", "pass"], "4308989841607208") 735 test_cli("cc_decrypt", ["4308989841607208", "pass"], "8028028028028027") 736 737def cli_cert_issuance_tests(tmp_dir): 738 root_key = os.path.join(tmp_dir, 'root.key') 739 root_crt = os.path.join(tmp_dir, 'root.crt') 740 int_key = os.path.join(tmp_dir, 'int.key') 741 int_crt = os.path.join(tmp_dir, 'int.crt') 742 int_csr = os.path.join(tmp_dir, 'int.csr') 743 leaf_key = os.path.join(tmp_dir, 'leaf.key') 744 leaf_crt = os.path.join(tmp_dir, 'leaf.crt') 745 leaf_csr = os.path.join(tmp_dir, 'leaf.csr') 746 747 test_cli("keygen", ["--params=2048", "--output=" + root_key], "") 748 test_cli("keygen", ["--params=2048", "--output=" + int_key], "") 749 test_cli("keygen", ["--params=2048", "--output=" + leaf_key], "") 750 751 test_cli("gen_self_signed", 752 [root_key, "Root", "--ca", "--path-limit=2", "--output="+root_crt], "") 753 754 test_cli("gen_pkcs10", "%s Intermediate --ca --output=%s" % (int_key, int_csr)) 755 test_cli("sign_cert", "%s %s %s --output=%s" % (root_crt, root_key, int_csr, int_crt)) 756 757 test_cli("gen_pkcs10", "%s Leaf --output=%s" % (leaf_key, leaf_csr)) 758 test_cli("sign_cert", "%s %s %s --output=%s" % (int_crt, int_key, leaf_csr, leaf_crt)) 759 760 test_cli("cert_verify" "%s %s %s" % (leaf_crt, int_crt, root_crt), "Certificate passes validation checks") 761 762def cli_timing_test_tests(_tmp_dir): 763 764 timing_tests = ["bleichenbacher", "manger", 765 "ecdsa", "ecc_mul", "inverse_mod", "pow_mod", 766 "lucky13sec3", "lucky13sec4sha1", 767 "lucky13sec4sha256", "lucky13sec4sha384"] 768 769 output_re = re.compile('[0-9]+;[0-9];[0-9]+') 770 771 for suite in timing_tests: 772 output = test_cli("timing_test", [suite, "--measurement-runs=16", "--warmup-runs=3"], None).split('\n') 773 774 for line in output: 775 if output_re.match(line) is None: 776 logging.error("Unexpected output in timing_test %s: %s", suite, line) 777 778def cli_tls_ciphersuite_tests(_tmp_dir): 779 policies = ['default', 'suiteb_128', 'suiteb_192', 'strict', 'all'] 780 781 versions = ['tls1.0', 'tls1.1', 'tls1.2'] 782 783 ciphersuite_re = re.compile('^[A-Z0-9_]+$') 784 785 for policy in policies: 786 for version in versions: 787 788 if version != 'tls1.2' and policy != 'all': 789 continue 790 791 output = test_cli("tls_ciphers", ["--version=" + version, "--policy=" + policy], None).split('\n') 792 793 for line in output: 794 if ciphersuite_re.match(line) is None: 795 logging.error("Unexpected ciphersuite line %s", line) 796 797def cli_asn1_tests(_tmp_dir): 798 input_pem = """-----BEGIN BLOB----- 799MCACAQUTBnN0cmluZzEGAQH/AgFjBAUAAAAAAAMEAP///w== 800-----END BLOB------ 801""" 802 803 expected = """d= 0, l= 32: SEQUENCE 804 d= 1, l= 1: INTEGER 05 805 d= 1, l= 6: PRINTABLE STRING string 806 d= 1, l= 6: SET 807 d= 2, l= 1: BOOLEAN true 808 d= 2, l= 1: INTEGER 63 809 d= 1, l= 5: OCTET STRING 0000000000 810 d= 1, l= 4: BIT STRING FFFFFF""" 811 812 test_cli("asn1print", "--pem -", expected, input_pem) 813 814def cli_tls_socket_tests(tmp_dir): 815 client_msg = b'Client message %d\n' % (random.randint(0, 2**128)) 816 server_port = random_port_number() 817 818 priv_key = os.path.join(tmp_dir, 'priv.pem') 819 ca_cert = os.path.join(tmp_dir, 'ca.crt') 820 crt_req = os.path.join(tmp_dir, 'crt.req') 821 server_cert = os.path.join(tmp_dir, 'server.crt') 822 823 test_cli("keygen", ["--algo=ECDSA", "--params=secp256r1", "--output=" + priv_key], "") 824 825 test_cli("gen_self_signed", 826 [priv_key, "CA", "--ca", "--country=VT", 827 "--dns=ca.example", "--hash=SHA-384", "--output="+ca_cert], 828 "") 829 830 test_cli("cert_verify", ca_cert, "Certificate did not validate - Cannot establish trust") 831 832 test_cli("gen_pkcs10", "%s localhost --output=%s" % (priv_key, crt_req)) 833 834 test_cli("sign_cert", "%s %s %s --output=%s" % (ca_cert, priv_key, crt_req, server_cert)) 835 836 tls_server = subprocess.Popen([CLI_PATH, 'tls_server', '--max-clients=1', 837 '--port=%d' % (server_port), server_cert, priv_key], 838 stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 839 840 wait_time = 1.0 841 842 time.sleep(wait_time) 843 844 tls_client = subprocess.Popen([CLI_PATH, 'tls_client', 'localhost', 845 '--port=%d' % (server_port), '--trusted-cas=%s' % (ca_cert)], 846 stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 847 848 time.sleep(wait_time) 849 850 tls_client.stdin.write(client_msg) 851 tls_client.stdin.flush() 852 853 time.sleep(wait_time) 854 855 (stdout, stderr) = tls_client.communicate() 856 857 if stderr: 858 logging.error("Got unexpected stderr output %s" % (stderr)) 859 860 if b'Handshake complete' not in stdout: 861 logging.error('Failed to complete handshake: %s' % (stdout)) 862 863 if client_msg not in stdout: 864 logging.error("Missing client message from stdout %s" % (stdout)) 865 866 tls_server.communicate() 867 868def cli_tls_http_server_tests(tmp_dir): 869 if not check_for_command("tls_http_server"): 870 return 871 872 try: 873 from http.client import HTTPSConnection 874 except ImportError: 875 try: 876 from httplib import HTTPSConnection 877 except ImportError: 878 return 879 import ssl 880 881 server_port = random_port_number() 882 883 priv_key = os.path.join(tmp_dir, 'priv.pem') 884 ca_cert = os.path.join(tmp_dir, 'ca.crt') 885 crt_req = os.path.join(tmp_dir, 'crt.req') 886 server_cert = os.path.join(tmp_dir, 'server.crt') 887 888 test_cli("keygen", ["--algo=ECDSA", "--params=secp384r1", "--output=" + priv_key], "") 889 890 test_cli("gen_self_signed", 891 [priv_key, "CA", "--ca", "--country=VT", 892 "--dns=ca.example", "--hash=SHA-384", "--output="+ca_cert], 893 "") 894 895 test_cli("gen_pkcs10", "%s localhost --output=%s" % (priv_key, crt_req)) 896 897 test_cli("sign_cert", "%s %s %s --output=%s" % (ca_cert, priv_key, crt_req, server_cert)) 898 899 tls_server = subprocess.Popen([CLI_PATH, 'tls_http_server', '--max-clients=2', 900 '--port=%d' % (server_port), server_cert, priv_key], 901 stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 902 903 wait_time = 1.0 904 time.sleep(wait_time) 905 906 context = ssl.create_default_context(cafile=ca_cert) 907 conn = HTTPSConnection('localhost', port=server_port, context=context) 908 conn.request("GET", "/") 909 resp = conn.getresponse() 910 911 if resp.status != 200: 912 logging.error('Unexpected response status %d' % (resp.status)) 913 914 body = str(resp.read()) 915 916 if body.find('TLS negotiation with Botan 2.') < 0: 917 logging.error('Unexpected response body') 918 919 conn.request("POST", "/logout") 920 resp = conn.getresponse() 921 922 if resp.status != 405: 923 logging.error('Unexpected response status %d' % (resp.status)) 924 925 if sys.version_info.major >= 3: 926 rc = tls_server.wait(5) # pylint: disable=too-many-function-args 927 else: 928 rc = tls_server.wait() 929 930 if rc != 0: 931 logging.error("Unexpected return code from https_server %d", rc) 932 933def cli_tls_proxy_tests(tmp_dir): 934 # pylint: disable=too-many-locals,too-many-statements 935 if not check_for_command("tls_proxy"): 936 return 937 938 try: 939 from http.client import HTTPSConnection 940 except ImportError: 941 try: 942 from httplib import HTTPSConnection 943 except ImportError: 944 return 945 946 try: 947 from http.server import HTTPServer, BaseHTTPRequestHandler 948 except ImportError: 949 try: 950 from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler 951 except ImportError: 952 return 953 954 import ssl 955 import threading 956 957 server_port = random_port_number() 958 proxy_port = random_port_number() 959 960 while server_port == proxy_port: 961 proxy_port = random_port_number() 962 963 priv_key = os.path.join(tmp_dir, 'priv.pem') 964 ca_cert = os.path.join(tmp_dir, 'ca.crt') 965 crt_req = os.path.join(tmp_dir, 'crt.req') 966 server_cert = os.path.join(tmp_dir, 'server.crt') 967 968 test_cli("keygen", ["--algo=ECDSA", "--params=secp384r1", "--output=" + priv_key], "") 969 970 test_cli("gen_self_signed", 971 [priv_key, "CA", "--ca", "--country=VT", 972 "--dns=ca.example", "--hash=SHA-384", "--output="+ca_cert], 973 "") 974 975 test_cli("gen_pkcs10", "%s localhost --output=%s" % (priv_key, crt_req)) 976 977 test_cli("sign_cert", "%s %s %s --output=%s" % (ca_cert, priv_key, crt_req, server_cert)) 978 979 tls_proxy = subprocess.Popen([CLI_PATH, 'tls_proxy', str(proxy_port), '127.0.0.1', str(server_port), 980 server_cert, priv_key, '--output=/tmp/proxy.err', '--max-clients=2'], 981 stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 982 983 wait_time = 1.0 984 985 time.sleep(wait_time) 986 987 server_response = binascii.hexlify(os.urandom(32)) 988 989 def run_http_server(): 990 class Handler(BaseHTTPRequestHandler): 991 992 def do_GET(self): # pylint: disable=invalid-name 993 self.send_response(200) 994 self.end_headers() 995 self.wfile.write(server_response) 996 997 httpd = HTTPServer(('', server_port), Handler) 998 httpd.serve_forever() 999 1000 http_thread = threading.Thread(target=run_http_server) 1001 http_thread.daemon = True 1002 http_thread.start() 1003 1004 time.sleep(wait_time) 1005 1006 context = ssl.create_default_context(cafile=ca_cert) 1007 1008 for _i in range(2): 1009 conn = HTTPSConnection('localhost', port=proxy_port, context=context) 1010 conn.request("GET", "/") 1011 resp = conn.getresponse() 1012 1013 if resp.status != 200: 1014 logging.error('Unexpected response status %d' % (resp.status)) 1015 1016 body = resp.read() 1017 1018 if body != server_response: 1019 logging.error('Unexpected response from server %s' % (body)) 1020 1021 if sys.version_info.major >= 3: 1022 rc = tls_proxy.wait(5) # pylint: disable=too-many-function-args 1023 else: 1024 rc = tls_proxy.wait() 1025 1026 if rc != 0: 1027 logging.error('Unexpected return code %d', rc) 1028 1029def cli_trust_root_tests(tmp_dir): 1030 pem_file = os.path.join(tmp_dir, 'pems') 1031 dn_file = os.path.join(tmp_dir, 'dns') 1032 1033 test_cli("trust_roots", ['--dn-only', '--output=%s' % (dn_file)], "") 1034 1035 dn_re = re.compile('(.+=\".+\")(,.+=\".+\")') 1036 for line in open(dn_file): 1037 if dn_re.match(line) is None: 1038 logging.error("Unexpected DN line %s", line) 1039 1040 test_cli("trust_roots", ['--output=%s' % (pem_file)], "") 1041 1042def cli_tss_tests(tmp_dir): 1043 data_file = os.path.join(tmp_dir, 'data') 1044 1045 exp_hash = "53B3C59276AE30EA7FD882268E80FD96AD80CC9FEB15F9FB940E7C4B5CF80B9E" 1046 1047 test_cli("rng", ["32", "--output=%s" % (data_file)], "") 1048 test_cli("hash", ["--no-fsname", data_file], exp_hash) 1049 1050 m = 3 1051 n = 5 1052 1053 test_cli("tss_split", [str(m), str(n), data_file, "--share-prefix=%s/split" % (tmp_dir)], "") 1054 1055 share_files = [] 1056 1057 for i in range(1, n+1): 1058 share = os.path.join(tmp_dir, "split%d.tss" % (i)) 1059 if not os.access(share, os.R_OK): 1060 logging.error("Failed to create expected split file %s", share) 1061 share_files.append(share) 1062 1063 rec5 = os.path.join(tmp_dir, "recovered_5") 1064 test_cli("tss_recover", share_files + ["--output=%s" % (rec5)], "") 1065 test_cli("hash", ["--no-fsname", rec5], exp_hash) 1066 1067 rec4 = os.path.join(tmp_dir, "recovered_4") 1068 test_cli("tss_recover", share_files[1:] + ["--output=%s" % (rec4)], "") 1069 test_cli("hash", ["--no-fsname", rec4], exp_hash) 1070 1071 rec3 = os.path.join(tmp_dir, "recovered_3") 1072 test_cli("tss_recover", share_files[2:] + ["--output=%s" % (rec3)], "") 1073 test_cli("hash", ["--no-fsname", rec3], exp_hash) 1074 1075 rec2 = os.path.join(tmp_dir, "recovered_2") 1076 test_cli("tss_recover", share_files[3:] + ["--output=%s" % (rec2)], "", None, 1077 b'Error: Insufficient shares to do TSS reconstruction\n') 1078 1079 1080def cli_pk_encrypt_tests(tmp_dir): 1081 input_file = os.path.join(tmp_dir, 'input') 1082 ctext_file = os.path.join(tmp_dir, 'ctext') 1083 recovered_file = os.path.join(tmp_dir, 'recovered') 1084 rsa_priv_key = os.path.join(tmp_dir, 'rsa.priv') 1085 rsa_pub_key = os.path.join(tmp_dir, 'rsa.pub') 1086 1087 test_cli("keygen", ["--algo=RSA", "--provider=base", "--params=2048", "--output=%s" % (rsa_priv_key)], "") 1088 1089 key_hash = "72AF3227EF57A728E894D54623EB8E2C0CD11A4A98BF2DF32DB052BF60897873" 1090 test_cli("hash", ["--no-fsname", "--algo=SHA-256", rsa_priv_key], key_hash) 1091 1092 test_cli("pkcs8", ["--pub-out", "%s/rsa.priv" % (tmp_dir), "--output=%s" % (rsa_pub_key)], "") 1093 1094 # Generate a random input file 1095 test_cli("rng", ["10", "16", "32", "--output=%s" % (input_file)], "") 1096 1097 # Because we used a fixed DRBG for each invocation the same ctext is generated each time 1098 rng_output_hash = "32F5E7B61357DE8397EFDA1E598379DFD5EE21767BDF4E2A435F05117B836AC6" 1099 ctext_hash = "FF1F0EEC2C42DD61D78505C5DF624A19AE6FE2BAB0B8F7D878C7655D54C68FE0" 1100 1101 test_cli("hash", ["--no-fsname", "--algo=SHA-256", input_file], rng_output_hash) 1102 1103 # Encrypt and verify ciphertext is the expected value 1104 test_cli("pk_encrypt", [rsa_pub_key, input_file, "--output=%s" % (ctext_file)], "") 1105 test_cli("hash", ["--no-fsname", "--algo=SHA-256", ctext_file], ctext_hash) 1106 1107 # Decrypt and verify plaintext is recovered 1108 test_cli("pk_decrypt", [rsa_priv_key, ctext_file, "--output=%s" % (recovered_file)], "") 1109 test_cli("hash", ["--no-fsname", "--algo=SHA-256", recovered_file], rng_output_hash) 1110 1111def cli_uuid_tests(_tmp_dir): 1112 test_cli("uuid", [], "D80F88F6-ADBE-45AC-B10C-3602E67D985B") 1113 1114 uuid_re = re.compile(r'[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}') 1115 1116 output = test_cli("uuid", []) 1117 1118 if uuid_re.match(output) is None: 1119 logging.error('Bad uuid output %s' % (output)) 1120 1121def cli_tls_client_hello_tests(_tmp_dir): 1122 1123 # pylint: disable=line-too-long 1124 chello = "16030100cf010000cb03035b3cf2457b864d7bef2a4b1f84fc3ced2b68d9551f3455ffdd305af277a91bb200003a16b816b716ba16b9cca9cca8c02cc030c02bc02fc0adc0acc024c00ac028c014c023c009c027c013ccaa009f009ec09fc09e006b003900670033010000680000000e000c000009676d61696c2e636f6d000500050100000000000a001a0018001d0017001a0018001b0019001c01000101010201030104000b00020100000d00140012080508040806050106010401050306030403001600000017000000230000ff01000100" 1125 1126 output = test_cli("tls_client_hello", ["--hex", "-"], None, chello) 1127 1128 output_hash = "8EBFC3205ACFA98461128FE5D081D19254237AF84F7DAF000A3C992C3CF6DE44" 1129 test_cli("hash", ["--no-fsname", "--algo=SHA-256", "-"], output_hash, output) 1130 1131def cli_speed_pk_tests(_tmp_dir): 1132 msec = 1 1133 1134 pk_algos = ["ECDSA", "ECDH", "SM2", "ECKCDSA", "ECGDSA", "GOST-34.10", 1135 "DH", "DSA", "ElGamal", "Ed25519", "Curve25519", "NEWHOPE", "McEliece", 1136 "RSA", "RSA_keygen", "XMSS"] 1137 1138 output = test_cli("speed", ["--msec=%d" % (msec)] + pk_algos, None).split('\n') 1139 1140 # ECDSA-secp256r1 106 keygen/sec; 9.35 ms/op 37489733 cycles/op (1 op in 9 ms) 1141 format_re = re.compile(r'^.* [0-9]+ ([A-Za-z ]+)/sec; [0-9]+\.[0-9]+ ms/op .*\([0-9]+ (op|ops) in [0-9\.]+ ms\)') 1142 for line in output: 1143 if format_re.match(line) is None: 1144 logging.error("Unexpected line %s", line) 1145 1146def cli_speed_pbkdf_tests(_tmp_dir): 1147 msec = 1 1148 pbkdf_ops = ['bcrypt', 'passhash9', 'argon2'] 1149 1150 format_re = re.compile(r'^.* [0-9]+ /sec; [0-9]+\.[0-9]+ ms/op .*\([0-9]+ (op|ops) in [0-9]+(\.[0-9]+)? ms\)') 1151 for op in pbkdf_ops: 1152 output = test_cli("speed", ["--msec=%d" % (msec), op], None).split('\n') 1153 for line in output: 1154 if format_re.match(line) is None: 1155 logging.error("Unexpected line %s", line) 1156 1157def cli_speed_table_tests(_tmp_dir): 1158 msec = 1 1159 1160 version_re = re.compile(r'^Botan 2\.[0-9]+\.[0-9] \(.*, revision .*, distribution .*\)') 1161 cpuid_re = re.compile(r'^CPUID: [a-z_0-9 ]*$') 1162 format_re = re.compile(r'^AES-128 .* buffer size [0-9]+ bytes: [0-9]+\.[0-9]+ MiB\/sec .*\([0-9]+\.[0-9]+ MiB in [0-9]+\.[0-9]+ ms\)') 1163 tbl_hdr_re = re.compile(r'^algo +operation +1024 bytes$') 1164 tbl_val_re = re.compile(r'^AES-128 +(encrypt|decrypt) +[0-9]+(\.[0-9]{2})$') 1165 1166 output = test_cli("speed", ["--format=table", "--provider=base", "--msec=%d" % (msec), "AES-128"], None).split('\n') 1167 1168 if len(output) != 11: 1169 logging.error('Unexpected number of lines from table output') 1170 1171 if version_re.match(output[0]) is None: 1172 logging.error("Unexpected version line %s", output[0]) 1173 1174 if output[1] != '': 1175 if cpuid_re.match(output[1]) is None: 1176 logging.error("Unexpected cpuid line %s", output[1]) 1177 elif output[2] != '': 1178 logging.error("Expected newline got %s", output[2]) 1179 1180 if format_re.match(output[3]) is None: 1181 logging.error("Unexpected line %s", output[3]) 1182 if format_re.match(output[4]) is None: 1183 logging.error("Unexpected line %s", output[4]) 1184 if output[5] != '': 1185 logging.error("Expected newline got %s", output[5]) 1186 1187 if tbl_hdr_re.match(output[6]) is None: 1188 logging.error("Unexpected table header %s", output[6]) 1189 if tbl_val_re.match(output[7]) is None: 1190 logging.error("Unexpected table header %s", output[7]) 1191 if tbl_val_re.match(output[8]) is None: 1192 logging.error("Unexpected table header %s", output[8]) 1193 if output[9] != '': 1194 logging.error("Expected newline got %s", output[9]) 1195 if output[10].find('results are the number of 1000s bytes processed per second') < 0: 1196 logging.error("Unexpected trailing message got %s", output[10]) 1197 1198def cli_speed_invalid_option_tests(_tmp_dir): 1199 speed_usage = b"Usage: speed --msec=500 --format=default --ecc-groups= --provider= --buf-size=1024 --clear-cpuid= --cpu-clock-speed=0 --cpu-clock-ratio=1.0 *algos\n" 1200 1201 test_cli("speed", ["--buf-size=0", "--msec=1", "AES-128"], 1202 expected_stderr=b"Usage error: Cannot have a zero-sized buffer\n%s" % (speed_usage)) 1203 1204 test_cli("speed", ["--buf-size=F00F", "--msec=1", "AES-128"], 1205 expected_stderr=b"Usage error: Invalid integer value 'F00F' for option buf-size\n%s" % (speed_usage)) 1206 1207 test_cli("speed", ["--buf-size=90000000", "--msec=1", "AES-128"], 1208 expected_stderr=b"Usage error: Specified buffer size is too large\n%s" % (speed_usage)) 1209 1210 test_cli("speed", ["--clear-cpuid=goku", "--msec=1", "AES-128"], 1211 expected_stderr=b"Warning don't know CPUID flag 'goku'\n") 1212 1213def cli_speed_math_tests(_tmp_dir): 1214 msec = 1 1215 # these all have a common output format 1216 math_ops = ['mp_mul', 'mp_div', 'mp_div10', 'modexp', 'random_prime', 'inverse_mod', 1217 'rfc3394', 'fpe_fe1', 'ecdsa_recovery', 'ecc_init', 'poly_dbl', 1218 'bn_redc', 'nistp_redc', 'ecc_mult', 'ecc_ops', 'os2ecp', 'primality_test'] 1219 1220 format_re = re.compile(r'^.* [0-9]+ /sec; [0-9]+\.[0-9]+ ms/op .*\([0-9]+ (op|ops) in [0-9]+(\.[0-9]+)? ms\)') 1221 for op in math_ops: 1222 output = test_cli("speed", ["--msec=%d" % (msec), op], None).split('\n') 1223 for line in output: 1224 if format_re.match(line) is None: 1225 logging.error("Unexpected line %s", line) 1226 1227def cli_speed_tests(_tmp_dir): 1228 # pylint: disable=too-many-branches 1229 1230 msec = 1 1231 1232 output = test_cli("speed", ["--msec=%d" % (msec), "--buf-size=64,512", "AES-128"], None).split('\n') 1233 1234 if len(output) % 4 != 0: 1235 logging.error("Unexpected number of lines for AES-128 speed test") 1236 1237 # pylint: disable=line-too-long 1238 format_re = re.compile(r'^AES-128 .* buffer size [0-9]+ bytes: [0-9]+\.[0-9]+ MiB\/sec .*\([0-9]+\.[0-9]+ MiB in [0-9]+\.[0-9]+ ms\)') 1239 for line in output: 1240 if format_re.match(line) is None: 1241 logging.error("Unexpected line %s", line) 1242 1243 output = test_cli("speed", ["--msec=%d" % (msec), "ChaCha20", "SHA-256", "HMAC(SHA-256)"], None).split('\n') 1244 1245 # pylint: disable=line-too-long 1246 format_re = re.compile(r'^.* buffer size [0-9]+ bytes: [0-9]+\.[0-9]+ MiB\/sec .*\([0-9]+\.[0-9]+ MiB in [0-9]+\.[0-9]+ ms\)') 1247 for line in output: 1248 if format_re.match(line) is None: 1249 logging.error("Unexpected line %s", line) 1250 1251 output = test_cli("speed", ["--msec=%d" % (msec), "AES-128/GCM"], None).split('\n') 1252 format_re_ks = re.compile(r'^AES-128/GCM\(16\).* [0-9]+ key schedule/sec; [0-9]+\.[0-9]+ ms/op .*\([0-9]+ (op|ops) in [0-9\.]+ ms\)') 1253 format_re_cipher = re.compile(r'^AES-128/GCM\(16\) .* buffer size [0-9]+ bytes: [0-9]+\.[0-9]+ MiB\/sec .*\([0-9]+\.[0-9]+ MiB in [0-9]+\.[0-9]+ ms\)') 1254 for line in output: 1255 if format_re_ks.match(line) is None: 1256 if format_re_cipher.match(line) is None: 1257 logging.error('Unexpected line %s', line) 1258 1259 output = test_cli("speed", ["--msec=%d" % (msec), "scrypt"], None).split('\n') 1260 1261 format_re = re.compile(r'^scrypt-[0-9]+-[0-9]+-[0-9]+ \([0-9]+ MiB\) [0-9]+ /sec; [0-9]+\.[0-9]+ ms/op .*\([0-9]+ (op|ops) in [0-9\.]+ ms\)') 1262 1263 for line in output: 1264 if format_re.match(line) is None: 1265 logging.error("Unexpected line %s", line) 1266 1267 output = test_cli("speed", ["--msec=%d" % (msec), "RNG"], None).split('\n') 1268 1269 # ChaCha_RNG generate buffer size 1024 bytes: 954.431 MiB/sec 4.01 cycles/byte (477.22 MiB in 500.00 ms) 1270 format_re = re.compile(r'^.* generate buffer size [0-9]+ bytes: [0-9]+\.[0-9]+ MiB/sec .*\([0-9]+\.[0-9]+ MiB in [0-9]+\.[0-9]+ ms') 1271 for line in output: 1272 if format_re.match(line) is None: 1273 logging.error("Unexpected line %s", line) 1274 1275 # Entropy source rdseed output 128 bytes estimated entropy 0 in 0.02168 ms total samples 32 1276 output = test_cli("speed", ["--msec=%d" % (msec), "entropy"], None).split('\n') 1277 format_re = re.compile(r'^Entropy source [_a-z0-9]+ output [0-9]+ bytes estimated entropy [0-9]+ in [0-9]+\.[0-9]+ ms .*total samples [0-9]+') 1278 for line in output: 1279 if format_re.match(line) is None: 1280 logging.error("Unexpected line %s", line) 1281 1282 output = test_cli("speed", ["--msec=%d" % (msec), "--format=json", "AES-128"], None) 1283 1284 json_blob = json.loads(output) 1285 if len(json_blob) < 2: 1286 logging.error("Unexpected size for JSON output") 1287 1288 for b in json_blob: 1289 for field in ['algo', 'op', 'events', 'bps', 'buf_size', 'nanos']: 1290 if field not in b: 1291 logging.error('Missing field %s in JSON record %s' % (field, b)) 1292 1293def run_test(fn_name, fn): 1294 start = time.time() 1295 tmp_dir = tempfile.mkdtemp(prefix='botan_cli_') 1296 try: 1297 fn(tmp_dir) 1298 except Exception as e: # pylint: disable=broad-except 1299 logging.error("Test %s threw exception: %s", fn_name, e) 1300 1301 shutil.rmtree(tmp_dir) 1302 end = time.time() 1303 logging.info("Ran %s in %.02f sec", fn_name, end-start) 1304 1305def main(args=None): 1306 # pylint: disable=too-many-branches,too-many-locals 1307 if args is None: 1308 args = sys.argv 1309 1310 parser = optparse.OptionParser( 1311 formatter=optparse.IndentedHelpFormatter(max_help_position=50)) 1312 1313 parser.add_option('--verbose', action='store_true', default=False) 1314 parser.add_option('--quiet', action='store_true', default=False) 1315 parser.add_option('--threads', action='store', type='int', default=0) 1316 1317 (options, args) = parser.parse_args(args) 1318 1319 setup_logging(options) 1320 1321 if len(args) < 2: 1322 logging.error("Usage: %s path_to_botan_cli [test_regex]", args[0]) 1323 return 1 1324 1325 if not os.access(args[1], os.X_OK): 1326 logging.error("Could not access/execute %s", args[1]) 1327 return 2 1328 1329 threads = options.threads 1330 if threads == 0: 1331 threads = multiprocessing.cpu_count() 1332 1333 global CLI_PATH 1334 CLI_PATH = args[1] 1335 1336 test_regex = None 1337 if len(args) == 3: 1338 try: 1339 test_regex = re.compile(args[2]) 1340 except re.error as e: 1341 logging.error("Invalid regex: %s", str(e)) 1342 return 1 1343 1344 # some of the slowest tests are grouped up front 1345 test_fns = [ 1346 cli_speed_tests, 1347 cli_speed_pk_tests, 1348 cli_speed_math_tests, 1349 cli_speed_pbkdf_tests, 1350 cli_speed_table_tests, 1351 cli_speed_invalid_option_tests, 1352 cli_xmss_sign_tests, 1353 1354 cli_argon2_tests, 1355 cli_asn1_tests, 1356 cli_base32_tests, 1357 cli_base58_tests, 1358 cli_base64_tests, 1359 cli_bcrypt_tests, 1360 cli_cc_enc_tests, 1361 cli_cycle_counter, 1362 cli_cert_issuance_tests, 1363 cli_compress_tests, 1364 cli_config_tests, 1365 cli_cpuid_tests, 1366 cli_dl_group_info_tests, 1367 cli_ec_group_info_tests, 1368 cli_entropy_tests, 1369 cli_factor_tests, 1370 cli_gen_dl_group_tests, 1371 cli_gen_prime_tests, 1372 cli_hash_tests, 1373 cli_help_tests, 1374 cli_hex_tests, 1375 cli_hmac_tests, 1376 cli_is_prime_tests, 1377 cli_key_tests, 1378 cli_mod_inverse_tests, 1379 cli_pbkdf_tune_tests, 1380 cli_pk_encrypt_tests, 1381 cli_pk_workfactor_tests, 1382 cli_psk_db_tests, 1383 cli_rng_tests, 1384 cli_roughtime_check_tests, 1385 cli_roughtime_tests, 1386 cli_timing_test_tests, 1387 cli_tls_ciphersuite_tests, 1388 cli_tls_client_hello_tests, 1389 cli_tls_http_server_tests, 1390 cli_tls_proxy_tests, 1391 cli_tls_socket_tests, 1392 cli_trust_root_tests, 1393 cli_tss_tests, 1394 cli_uuid_tests, 1395 cli_version_tests, 1396 ] 1397 1398 tests_to_run = [] 1399 for fn in test_fns: 1400 fn_name = fn.__name__ 1401 1402 if test_regex is None or test_regex.search(fn_name) is not None: 1403 tests_to_run.append((fn_name, fn)) 1404 1405 start_time = time.time() 1406 1407 if threads > 1: 1408 pool = ThreadPool(processes=threads) 1409 results = [] 1410 for test in tests_to_run: 1411 results.append(pool.apply_async(run_test, test)) 1412 1413 for result in results: 1414 result.get() 1415 else: 1416 for test in tests_to_run: 1417 run_test(test[0], test[1]) 1418 1419 end_time = time.time() 1420 1421 print("Ran %d tests with %d failures in %.02f seconds" % ( 1422 TESTS_RUN, TESTS_FAILED, end_time - start_time)) 1423 1424 if TESTS_FAILED > 0: 1425 return 1 1426 return 0 1427 1428if __name__ == '__main__': 1429 sys.exit(main()) 1430